Funkční generování QR kódů
This commit is contained in:
parent
45c2f9e264
commit
55b9d1681e
@ -44,11 +44,12 @@ Aplikace sestává ze tří (čtyř) modulů.
|
|||||||
- [x] Implementovat Pizza day
|
- [x] Implementovat Pizza day
|
||||||
- [x] Umožnit uzamčení objednávek zakladatelem
|
- [x] Umožnit uzamčení objednávek zakladatelem
|
||||||
- [x] Možnost uložení čísla účtu
|
- [x] Možnost uložení čísla účtu
|
||||||
- [ ] Automatické generování a zobrazení QR kódů
|
- [x] Automatické generování a zobrazení QR kódů
|
||||||
- [ ] https://qr-platba.cz/pro-vyvojare/restful-api/
|
- [x] https://qr-platba.cz/pro-vyvojare/restful-api/
|
||||||
- [ ] Zobrazovat celkovou cenu objednávky pod tabulkou objednávek
|
- [ ] Zobrazovat celkovou cenu objednávky pod tabulkou objednávek
|
||||||
- [ ] Zobrazit upozornění před smazáním/zamknutím/odemknutím pizza day
|
- [ ] Zobrazit upozornění před smazáním/zamknutím/odemknutím pizza day
|
||||||
- [ ] Umožnit přidat k objednávce poznámku (např. "bez oliv")
|
- [ ] Umožnit přidat k objednávce poznámku (např. "bez oliv")
|
||||||
|
- [ ] Negenerovat QR kód pro objednávajícího
|
||||||
- [ ] Předvyplnění poslední vybrané hodnoty občas nefunguje, viz komentář
|
- [ ] Předvyplnění poslední vybrané hodnoty občas nefunguje, viz komentář
|
||||||
- [ ] Nasazení nové verze v Docker smaže veškerá data (protože data.json není venku)
|
- [ ] Nasazení nové verze v Docker smaže veškerá data (protože data.json není venku)
|
||||||
- [ ] Vylepšit dokumentaci projektu
|
- [ ] Vylepšit dokumentaci projektu
|
||||||
@ -59,7 +60,7 @@ Aplikace sestává ze tří (čtyř) modulů.
|
|||||||
- [ ] Nutno nejprve vyřešit předávání PHPSESSIONID cookie na pizzachefie.cz pomocí fetch()
|
- [ ] Nutno nejprve vyřešit předávání PHPSESSIONID cookie na pizzachefie.cz pomocí fetch()
|
||||||
- [ ] Přesunout autentizaci na server (JWT?)
|
- [ ] Přesunout autentizaci na server (JWT?)
|
||||||
- [x] Zavést .env.template a přidat .env do .gitignore
|
- [x] Zavést .env.template a přidat .env do .gitignore
|
||||||
- [ ] Zkrášlit dialog pro vyplnění čísla účtu, vypadá mizerně
|
- [x] Zkrášlit dialog pro vyplnění čísla účtu, vypadá mizerně
|
||||||
- [ ] Podpora pro notifikace v externích systémech (Gotify, Discord, MS Teams)
|
- [ ] Podpora pro notifikace v externích systémech (Gotify, Discord, MS Teams)
|
||||||
- [ ] Skripty pro snadné spuštění vývoje na Windows (ekvivalent ./run_dev.sh)
|
- [ ] Skripty pro snadné spuštění vývoje na Windows (ekvivalent ./run_dev.sh)
|
||||||
- [ ] Možnost náhledu na jiné dny v týdnu (např. pomocí šipek)
|
- [ ] Možnost náhledu na jiné dny v týdnu (např. pomocí šipek)
|
||||||
|
@ -18,6 +18,10 @@ const api = {
|
|||||||
post: <TBody extends BodyInit, TResponse>(url: string, body: TBody) => request<TResponse>(url, { method: 'POST', body, headers: { 'Content-Type': 'application/json' } }),
|
post: <TBody extends BodyInit, TResponse>(url: string, body: TBody) => request<TResponse>(url, { method: 'POST', body, headers: { 'Content-Type': 'application/json' } }),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getQrUrl = (login: string) => {
|
||||||
|
return `${getBaseUrl()}/api/qr?login=${login}`;
|
||||||
|
}
|
||||||
|
|
||||||
export const getData = async () => {
|
export const getData = async () => {
|
||||||
return await api.get<any>('/api/data');
|
return await api.get<any>('/api/data');
|
||||||
}
|
}
|
||||||
@ -50,8 +54,8 @@ export const finishOrder = async (login) => {
|
|||||||
return await api.post<any, any>('/api/finishOrder', JSON.stringify({ login }));
|
return await api.post<any, any>('/api/finishOrder', JSON.stringify({ login }));
|
||||||
}
|
}
|
||||||
|
|
||||||
export const finishDelivery = async (login) => {
|
export const finishDelivery = async (login, bankAccount, bankAccountHolder) => {
|
||||||
return await api.post<any, any>('/api/finishDelivery', JSON.stringify({ login }));
|
return await api.post<any, any>('/api/finishDelivery', JSON.stringify({ login, bankAccount, bankAccountHolder }));
|
||||||
}
|
}
|
||||||
|
|
||||||
export const updateChoice = async (name: string, choice: number | null) => {
|
export const updateChoice = async (name: string, choice: number | null) => {
|
||||||
|
@ -71,4 +71,8 @@
|
|||||||
color: rgb(0, 89, 255);
|
color: rgb(0, 89, 255);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qr-code {
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
@ -1,10 +1,10 @@
|
|||||||
import React, { useContext, useEffect, useMemo, useRef, useState } from 'react';
|
import React, { useContext, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import 'bootstrap/dist/css/bootstrap.min.css';
|
import 'bootstrap/dist/css/bootstrap.min.css';
|
||||||
import { EVENT_DISCONNECT, EVENT_MESSAGE, SocketContext } from './context/socket';
|
import { EVENT_DISCONNECT, EVENT_MESSAGE, SocketContext } from './context/socket';
|
||||||
import { addPizza, createPizzaDay, deletePizzaDay, finishDelivery, finishOrder, getData, getFood, getPizzy, lockPizzaDay, removePizza, unlockPizzaDay, updateChoice } from './Api';
|
import { addPizza, createPizzaDay, deletePizzaDay, finishDelivery, finishOrder, getData, getFood, getPizzy, getQrUrl, lockPizzaDay, removePizza, unlockPizzaDay, updateChoice } from './Api';
|
||||||
import { useAuth } from './context/auth';
|
import { useAuth } from './context/auth';
|
||||||
import Login from './Login';
|
import Login from './Login';
|
||||||
import { Locations, ClientData, Pizza, PizzaOrder, State } from './Types';
|
import { Locations, ClientData, Pizza, PizzaOrder, State, Order } from './Types';
|
||||||
import { Alert, Button, Col, Form, Row, Table } from 'react-bootstrap';
|
import { Alert, Button, Col, Form, Row, Table } from 'react-bootstrap';
|
||||||
import Header from './components/Header';
|
import Header from './components/Header';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
@ -14,16 +14,19 @@ import 'react-select-search/style.css';
|
|||||||
import './App.css';
|
import './App.css';
|
||||||
import { SelectSearchOption } from 'react-select-search';
|
import { SelectSearchOption } from 'react-select-search';
|
||||||
import { faTrashCan } from '@fortawesome/free-regular-svg-icons';
|
import { faTrashCan } from '@fortawesome/free-regular-svg-icons';
|
||||||
|
import { useBank } from './context/bank';
|
||||||
|
|
||||||
|
|
||||||
const EVENT_CONNECT = "connect"
|
const EVENT_CONNECT = "connect"
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const auth = useAuth();
|
const auth = useAuth();
|
||||||
|
const bank = useBank();
|
||||||
const [isConnected, setIsConnected] = useState<boolean>(false);
|
const [isConnected, setIsConnected] = useState<boolean>(false);
|
||||||
const [data, setData] = useState<ClientData>();
|
const [data, setData] = useState<ClientData>();
|
||||||
const [food, setFood] = useState<any>();
|
const [food, setFood] = useState<any>();
|
||||||
const [pizzy, setPizzy] = useState<Pizza[]>();
|
const [pizzy, setPizzy] = useState<Pizza[]>();
|
||||||
|
const [myOrder, setMyOrder] = useState<Order>();
|
||||||
const socket = useContext(SocketContext);
|
const socket = useContext(SocketContext);
|
||||||
const choiceRef = useRef<HTMLSelectElement>(null);
|
const choiceRef = useRef<HTMLSelectElement>(null);
|
||||||
|
|
||||||
@ -76,6 +79,16 @@ function App() {
|
|||||||
}
|
}
|
||||||
}, [auth, auth?.login, data?.choices])
|
}, [auth, auth?.login, data?.choices])
|
||||||
|
|
||||||
|
// Reference na mojí objednávku
|
||||||
|
useEffect(() => {
|
||||||
|
if (data?.pizzaDay?.orders) {
|
||||||
|
const myOrder = data.pizzaDay.orders.find(o => o.customer === auth?.login);
|
||||||
|
if (myOrder) {
|
||||||
|
setMyOrder(myOrder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [data?.pizzaDay?.orders])
|
||||||
|
|
||||||
const changeChoice = async (event: React.ChangeEvent<HTMLSelectElement>) => {
|
const changeChoice = async (event: React.ChangeEvent<HTMLSelectElement>) => {
|
||||||
const index = Object.values(Locations).indexOf(event.target.value as unknown as Locations);
|
const index = Object.values(Locations).indexOf(event.target.value as unknown as Locations);
|
||||||
if (auth?.login) {
|
if (auth?.login) {
|
||||||
@ -183,10 +196,8 @@ function App() {
|
|||||||
<Alert variant={'primary'}>
|
<Alert variant={'primary'}>
|
||||||
Poslední změny:
|
Poslední změny:
|
||||||
<ul>
|
<ul>
|
||||||
<li>Žárovka praskla a vyhodila hlavní jistič</li>
|
<li>Nová žárovka zatím funguje</li>
|
||||||
<li>Už jsem jí vyměnil</li>
|
<li>Funkční generování a zobrazení QR kódů pro Pizza day</li>
|
||||||
<li>Dobrá práce</li>
|
|
||||||
<li>Aplikace zatím nic nového neumí</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</Alert>
|
</Alert>
|
||||||
<h1 className='title'>Dnes je {data.date}</h1>
|
<h1 className='title'>Dnes je {data.date}</h1>
|
||||||
@ -288,7 +299,7 @@ function App() {
|
|||||||
await lockPizzaDay(auth.login);
|
await lockPizzaDay(auth.login);
|
||||||
}}>Vrátit do "uzamčeno"</Button>
|
}}>Vrátit do "uzamčeno"</Button>
|
||||||
<Button className='danger mb-3' style={{ marginLeft: '20px' }} title="Nastaví stav na 'Doručeno' - koncový stav." onClick={async () => {
|
<Button className='danger mb-3' style={{ marginLeft: '20px' }} title="Nastaví stav na 'Doručeno' - koncový stav." onClick={async () => {
|
||||||
await finishDelivery(auth.login);
|
await finishDelivery(auth.login, bank?.bankAccount, bank?.holderName);
|
||||||
}}>Doručeno</Button>
|
}}>Doručeno</Button>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@ -297,7 +308,7 @@ function App() {
|
|||||||
{
|
{
|
||||||
data.pizzaDay.state === State.DELIVERED &&
|
data.pizzaDay.state === State.DELIVERED &&
|
||||||
<div>
|
<div>
|
||||||
<p>Pizzy byly doručeny.</p>
|
<p>Pizzy byly doručeny. Objednávku můžete uhradit pomocí QR kódu níže.</p>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@ -309,6 +320,15 @@ function App() {
|
|||||||
disabled={data.pizzaDay.state !== State.CREATED}
|
disabled={data.pizzaDay.state !== State.CREATED}
|
||||||
/>
|
/>
|
||||||
<PizzaOrderList state={data.pizzaDay.state} orders={data.pizzaDay.orders} onDelete={handlePizzaDelete} />
|
<PizzaOrderList state={data.pizzaDay.state} orders={data.pizzaDay.orders} onDelete={handlePizzaDelete} />
|
||||||
|
{
|
||||||
|
data.pizzaDay.state === State.DELIVERED && myOrder &&
|
||||||
|
<div className='qr-code'>
|
||||||
|
<h3>QR platba</h3>
|
||||||
|
<div>Částka: {myOrder.totalPrice} Kč</div>
|
||||||
|
<img src={getQrUrl(auth.login)} alt='QR kód' />
|
||||||
|
<p>Generování QR kódů je v experimentální fázi - doporučujeme si překontrolovat údaje před odesláním platby.</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,6 +3,7 @@ import os from 'os';
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
import { formatDate } from './utils';
|
||||||
|
|
||||||
type PizzaSize = {
|
type PizzaSize = {
|
||||||
varId: number,
|
varId: number,
|
||||||
@ -85,10 +86,7 @@ const downloadPizzy = async () => {
|
|||||||
export const fetchPizzy = async (): Promise<Pizza[]> => {
|
export const fetchPizzy = async (): Promise<Pizza[]> => {
|
||||||
const tmpDir = os.tmpdir();
|
const tmpDir = os.tmpdir();
|
||||||
const date_ob = new Date();
|
const date_ob = new Date();
|
||||||
const date = ("0" + date_ob.getDate()).slice(-2);
|
const dateStr = formatDate(date_ob);
|
||||||
const month = ("0" + (date_ob.getMonth() + 1)).slice(-2);
|
|
||||||
const year = date_ob.getFullYear();
|
|
||||||
const dateStr = year + "-" + month + "-" + date;
|
|
||||||
const dataPath = path.join(tmpDir, `chefie-${dateStr}.json`);
|
const dataPath = path.join(tmpDir, `chefie-${dateStr}.json`);
|
||||||
|
|
||||||
if (fs.existsSync(dataPath)) {
|
if (fs.existsSync(dataPath)) {
|
||||||
|
@ -7,6 +7,7 @@ import { addPizzaOrder, createPizzaDay, deletePizzaDay, finishPizzaDelivery, fin
|
|||||||
import dotenv from 'dotenv';
|
import dotenv from 'dotenv';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { fetchMenus } from "./restaurants";
|
import { fetchMenus } from "./restaurants";
|
||||||
|
import { getQr } from "./qr";
|
||||||
|
|
||||||
const ENVIRONMENT = process.env.NODE_ENV || 'production'
|
const ENVIRONMENT = process.env.NODE_ENV || 'production'
|
||||||
dotenv.config({ path: path.resolve(__dirname, `../.env.${ENVIRONMENT}`) });
|
dotenv.config({ path: path.resolve(__dirname, `../.env.${ENVIRONMENT}`) });
|
||||||
@ -134,7 +135,7 @@ app.post("/api/finishDelivery", (req, res) => {
|
|||||||
if (!req.body?.login) {
|
if (!req.body?.login) {
|
||||||
throw Error("Nebyl předán login");
|
throw Error("Nebyl předán login");
|
||||||
}
|
}
|
||||||
const data = finishPizzaDelivery(req.body.login);
|
const data = finishPizzaDelivery(req.body.login, req.body.bankAccount, req.body.bankAccountHolder);
|
||||||
io.emit("message", data);
|
io.emit("message", data);
|
||||||
res.status(200).json({});
|
res.status(200).json({});
|
||||||
});
|
});
|
||||||
@ -148,6 +149,18 @@ app.post("/api/updateChoice", (req, res) => {
|
|||||||
res.status(200).json(data);
|
res.status(200).json(data);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.get("/api/qr", (req, res) => {
|
||||||
|
if (!req.query?.login || typeof req.query.login !== 'string') {
|
||||||
|
throw Error("Nebyl předán login");
|
||||||
|
}
|
||||||
|
const img = getQr(req.query.login);
|
||||||
|
res.writeHead(200, {
|
||||||
|
'Content-Type': 'image/png',
|
||||||
|
'Content-Length': img.length
|
||||||
|
});
|
||||||
|
res.end(img);
|
||||||
|
});
|
||||||
|
|
||||||
io.on("connection", (socket) => {
|
io.on("connection", (socket) => {
|
||||||
console.log(`New client connected: ${socket.id}`);
|
console.log(`New client connected: ${socket.id}`);
|
||||||
|
|
||||||
|
98
server/src/qr.ts
Normal file
98
server/src/qr.ts
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
import fs from "fs";
|
||||||
|
import axios from "axios";
|
||||||
|
import os from "os";
|
||||||
|
import path from "path";
|
||||||
|
import crypto from "crypto";
|
||||||
|
import { formatDate } from "./utils";
|
||||||
|
|
||||||
|
const QR_GENERATOR_URL = 'https://api.paylibo.com/paylibo/generator/image';
|
||||||
|
const COUNTRY_CODE = 'CZ';
|
||||||
|
const CURRENCY_CODE = 'CZK';
|
||||||
|
const QR_PIXEL_SIZE = 256;
|
||||||
|
const tmpDir = os.tmpdir();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Převede číslo účtu z BBAN do IBAN. Automaticky dopočítá kontrolní číslice.
|
||||||
|
*
|
||||||
|
* @param bankAccountNumber číslo účtu ve formátu BBAN (123456-0123456789/0100)
|
||||||
|
*/
|
||||||
|
function convertBbanToIban(bankAccountNumber: string): string {
|
||||||
|
// TODO validovat číslo účtu stejně jako na klientovi, pro případ že sem někdo pošle nesmysl
|
||||||
|
let prefix: string = '';
|
||||||
|
let accountNumber: string = bankAccountNumber;
|
||||||
|
if (bankAccountNumber.indexOf('-') >= 0) {
|
||||||
|
const split = bankAccountNumber.split('-');
|
||||||
|
prefix = split[0];
|
||||||
|
accountNumber = split[1];
|
||||||
|
}
|
||||||
|
prefix = prefix.padStart(6, '0');
|
||||||
|
const split = accountNumber.split('/');
|
||||||
|
accountNumber = split[0].padStart(10, '0');
|
||||||
|
const bankCode = split[1].padStart(4, '0');
|
||||||
|
let iban = `${bankCode}${prefix}${accountNumber}${COUNTRY_CODE}00`;
|
||||||
|
// Zatím napevno, nemá smysl řešit nic jiného než CZ
|
||||||
|
iban = iban.replace('C', '12').replace('Z', '35');
|
||||||
|
const remainder = BigInt(iban) % BigInt(97);
|
||||||
|
const checkDigits = BigInt(98) - remainder;
|
||||||
|
iban = `${COUNTRY_CODE}${checkDigits.toString()}${bankCode}${prefix}${accountNumber}`;
|
||||||
|
if (iban.length !== 24) {
|
||||||
|
throw Error("Neplatná délka sestaveného IBAN: " + iban.length + ", očekáváno 24");
|
||||||
|
}
|
||||||
|
return iban;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createNameHash(customerName: string): string {
|
||||||
|
return crypto.createHash('md5').update(customerName).digest('hex');
|
||||||
|
}
|
||||||
|
|
||||||
|
function createFilePath(nameHash: string): string {
|
||||||
|
const fileName = `${formatDate(new Date())}_${nameHash}.png`;
|
||||||
|
return path.join(tmpDir, fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vygeneruje, uloží a vrátí unikátní ID obrázku platebního QR kódu s danými parametry.
|
||||||
|
*
|
||||||
|
* @param customerName jméno uživatele, pro kterého je QR kód generován
|
||||||
|
* @param bankAccountNumber číslo cílového bankovního účtu ve formátu BBAN
|
||||||
|
* @param bankAccountHolder jméno držitele cílového bankovního účtu
|
||||||
|
* @param amount částka v Kč
|
||||||
|
* @param message zpráva pro příjemce
|
||||||
|
* @returns hash, pomocí kterého lze následně získat vygenerovaný obrázek
|
||||||
|
*/
|
||||||
|
export async function generateQr(customerName: string, bankAccountNumber: string, bankAccountHolder: string, amount: number, message: string): Promise<string> {
|
||||||
|
// Zpráva pro příjemce nesmí dle standardu obsahovat '*' a být delší než 60 znaků
|
||||||
|
if (message.indexOf('*') >= 0) {
|
||||||
|
message = message.replace('*', '');
|
||||||
|
}
|
||||||
|
if (message.length > 60) {
|
||||||
|
message = message.substring(0, 60);
|
||||||
|
}
|
||||||
|
const payload = {
|
||||||
|
iban: convertBbanToIban(bankAccountNumber),
|
||||||
|
amount,
|
||||||
|
currency: CURRENCY_CODE,
|
||||||
|
message,
|
||||||
|
recipientName: bankAccountHolder,
|
||||||
|
branding: false,
|
||||||
|
compress: false,
|
||||||
|
size: QR_PIXEL_SIZE,
|
||||||
|
}
|
||||||
|
const response = await axios.get(QR_GENERATOR_URL, { responseType: 'stream', params: { ...payload } });
|
||||||
|
// Použijeme hash, abychom nemuseli řešit nepovolené znaky ve jménu uživatele
|
||||||
|
const nameHash = createNameHash(customerName);
|
||||||
|
const imgPath = createFilePath(nameHash);
|
||||||
|
response.data.pipe(fs.createWriteStream(imgPath));
|
||||||
|
return nameHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vrátí obrázek s QR kódem, pokud existuje.
|
||||||
|
*
|
||||||
|
* @param customerName jméno uživatele
|
||||||
|
* @returns data obrázku
|
||||||
|
*/
|
||||||
|
export function getQr(customerName: string): Buffer {
|
||||||
|
const imgPath = createFilePath(createNameHash(customerName));
|
||||||
|
return fs.readFileSync(imgPath);
|
||||||
|
}
|
@ -1,7 +1,8 @@
|
|||||||
import {ClientData, Locations, Order, Pizza, PizzaDayState, PizzaOrder, PizzaSize, UdalostEnum} from "./types";
|
import { ClientData, Locations, Order, Pizza, PizzaDayState, PizzaOrder, PizzaSize, UdalostEnum } from "./types";
|
||||||
import {db} from "./database";
|
import { db } from "./database";
|
||||||
import {formatDate, getHumanDate, getIsWeekend} from "./utils";
|
import { formatDate, getHumanDate, getIsWeekend } from "./utils";
|
||||||
import {callNotifikace} from "./notifikace";
|
import { callNotifikace } from "./notifikace";
|
||||||
|
import { generateQr } from "./qr";
|
||||||
|
|
||||||
/** Vrátí dnešní datum, případně fiktivní datum pro účely vývoje a testování. */
|
/** Vrátí dnešní datum, případně fiktivní datum pro účely vývoje a testování. */
|
||||||
function getToday(): Date {
|
function getToday(): Date {
|
||||||
@ -191,17 +192,18 @@ export function finishPizzaOrder(login: string) {
|
|||||||
}
|
}
|
||||||
clientData.pizzaDay.state = PizzaDayState.ORDERED;
|
clientData.pizzaDay.state = PizzaDayState.ORDERED;
|
||||||
db.set(today, clientData);
|
db.set(today, clientData);
|
||||||
callNotifikace({input:{udalost:UdalostEnum.OBJEDNANA_PIZZA,user:clientData?.pizzaDay?.creator}})
|
callNotifikace({ input: { udalost: UdalostEnum.OBJEDNANA_PIZZA, user: clientData?.pizzaDay?.creator } })
|
||||||
return clientData;
|
return clientData;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Nastaví stav pizza day na "pizzy doručeny".
|
* Nastaví stav pizza day na "pizzy doručeny".
|
||||||
|
* Vygeneruje QR kódy pro všechny objednatele, pokud objednávající má vyplněné číslo účtu a jméno.
|
||||||
*
|
*
|
||||||
* @param login login uživatele
|
* @param login login uživatele
|
||||||
* @returns aktuální data pro uživatele
|
* @returns aktuální data pro uživatele
|
||||||
*/
|
*/
|
||||||
export function finishPizzaDelivery(login: string) {
|
export function finishPizzaDelivery(login: string, bankAccount?: string, bankAccountHolder?: string) {
|
||||||
const today = formatDate(getToday());
|
const today = formatDate(getToday());
|
||||||
const clientData: ClientData = db.get(today);
|
const clientData: ClientData = db.get(today);
|
||||||
if (!clientData.pizzaDay) {
|
if (!clientData.pizzaDay) {
|
||||||
@ -214,6 +216,15 @@ export function finishPizzaDelivery(login: string) {
|
|||||||
throw Error("Pizza day není ve stavu " + PizzaDayState.ORDERED);
|
throw Error("Pizza day není ve stavu " + PizzaDayState.ORDERED);
|
||||||
}
|
}
|
||||||
clientData.pizzaDay.state = PizzaDayState.DELIVERED;
|
clientData.pizzaDay.state = PizzaDayState.DELIVERED;
|
||||||
|
|
||||||
|
// Vygenerujeme QR kód, pokud k tomu máme data
|
||||||
|
if (bankAccount?.length && bankAccountHolder?.length) {
|
||||||
|
for (const order of clientData.pizzaDay.orders) {
|
||||||
|
let message = order.pizzaList.map(pizza => `Pizza ${pizza.name} (${pizza.size})`).join(', ');
|
||||||
|
const price = order.pizzaList.map(pizza => pizza.price).reduce((partial, a) => partial + a, 0);
|
||||||
|
generateQr(order.customer, bankAccount, bankAccountHolder, price, message).then(() => order.hasQr = true);
|
||||||
|
}
|
||||||
|
}
|
||||||
db.set(today, clientData);
|
db.set(today, clientData);
|
||||||
return clientData;
|
return clientData;
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,7 @@ export interface Order {
|
|||||||
customer: string, // jméno objednatele
|
customer: string, // jméno objednatele
|
||||||
pizzaList: PizzaOrder[], // seznam objednaných pizz
|
pizzaList: PizzaOrder[], // seznam objednaných pizz
|
||||||
totalPrice: number, // celková cena všech objednaných pizz a krabic
|
totalPrice: number, // celková cena všech objednaných pizz a krabic
|
||||||
|
hasQr?: boolean, // true, pokud je k objednávce vygenerován QR kód pro platbu
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Stav pizza dne */
|
/** Stav pizza dne */
|
||||||
|
Loading…
x
Reference in New Issue
Block a user