feat: add sale flow with Boopos Cloud
This commit is contained in:
parent
0a14fe8882
commit
9cb4302fc9
19 changed files with 449 additions and 398 deletions
|
|
@ -1,9 +1,16 @@
|
||||||
"use client";
|
"use client";
|
||||||
import { Box } from "@mui/material";
|
import { Box } from "@mui/material";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import MainLayout from "@layouts/MainLayout";
|
import MainLayout from "@layouts/MainLayout";
|
||||||
|
|
||||||
const CheckoutScreen: React.FC = () => {
|
interface CheckoutScreenProps {
|
||||||
|
onEndTransaction: () => void; // Callback para finalizar la transacción
|
||||||
|
}
|
||||||
|
|
||||||
|
const CheckoutScreen: React.FC<CheckoutScreenProps> = ({ onEndTransaction }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
|
|
@ -14,7 +21,7 @@ const CheckoutScreen: React.FC = () => {
|
||||||
boxShadow: '4'
|
boxShadow: '4'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<MainLayout />
|
<MainLayout onEndTransaction={onEndTransaction} />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,27 @@
|
||||||
"use client";
|
"use client";
|
||||||
import { Box, Button, Container } from "@mui/material";
|
import { Box, Button, Container } from "@mui/material";
|
||||||
import Grid from "@mui/material/Grid2";
|
import Grid from "@mui/material/Grid2";
|
||||||
import { useRouter } from "next/navigation";
|
import React, { useCallback, useState } from "react";
|
||||||
import React, { useCallback, useEffect } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import CheckoutScreen from "@app/selfcheckout/checkout/page";
|
||||||
import Title from "@atoms/Title";
|
import Title from "@atoms/Title";
|
||||||
|
|
||||||
const PreCheckout: React.FC = () => {
|
const PreCheckout: React.FC = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const router = useRouter();
|
const [stage, setStage] = useState<"precheckout" | "searching" | "checkout">("precheckout");
|
||||||
|
|
||||||
const handleStartCheckout = useCallback(() => {
|
const handleStartCheckout = useCallback(() => {
|
||||||
router.push("/selfcheckout/verifyAffiliation");
|
setStage("checkout");
|
||||||
}, [router]);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const handleKeyPress = () => {
|
const handleEndTransaction = () => {
|
||||||
handleStartCheckout();
|
setStage("precheckout");
|
||||||
};
|
};
|
||||||
|
|
||||||
document.addEventListener("keydown", handleKeyPress);
|
if (stage === "checkout") {
|
||||||
|
return <CheckoutScreen onEndTransaction={handleEndTransaction} />;
|
||||||
// Cleanup the event listener
|
}
|
||||||
return () => {
|
|
||||||
document.removeEventListener("keydown", handleKeyPress);
|
|
||||||
};
|
|
||||||
}, [handleStartCheckout]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container maxWidth="md" sx={{ textAlign: "center", marginTop: "20vh" }}>
|
<Container maxWidth="md" sx={{ textAlign: "center", marginTop: "20vh" }}>
|
||||||
|
|
|
||||||
11
package/components/atoms/states/StoreAtom.tsx
Normal file
11
package/components/atoms/states/StoreAtom.tsx
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { atom } from 'jotai';
|
||||||
|
|
||||||
|
export const storeNumberAtom = atom<string>(process.env.NEXT_PUBLIC_STORE_NUMBER || '');
|
||||||
|
export const storePosAtom = atom<string>(process.env.NEXT_PUBLIC_POS || '');
|
||||||
|
export const storeInfoPosAtom = atom({
|
||||||
|
addressPos: process.env.NEXT_PUBLIC_STORE_ADDRESS || '',
|
||||||
|
codeMunPos: process.env.NEXT_PUBLIC_STORE_CODE_CITY || '',
|
||||||
|
nameMunPos: process.env.NEXT_PUBLIC_STORE_CITY || '',
|
||||||
|
codeDepPos: process.env.NEXT_PUBLIC_STORE_CODE_DEPARTMENT || '',
|
||||||
|
nameDepPos: process.env.NEXT_PUBLIC_STORE_DEPARTMENT || '',
|
||||||
|
});
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
"use client"
|
|
||||||
import { Box, Card, CardContent } from "@mui/material"
|
|
||||||
import Grid from "@mui/material/Grid2"
|
|
||||||
import { useTranslation } from "react-i18next"
|
|
||||||
import Button from "@atoms/Button"
|
|
||||||
import Label from "@atoms/Label"
|
|
||||||
import Title from "@atoms/Title"
|
|
||||||
|
|
||||||
interface AskForAffiliationProps {
|
|
||||||
onAffiliationSelect: (isAffiliated: boolean) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
const AskForAffiliation: React.FC<AskForAffiliationProps> = ({ onAffiliationSelect }) => {
|
|
||||||
|
|
||||||
const { t } = useTranslation()
|
|
||||||
|
|
||||||
const handleYesClick = () => {
|
|
||||||
onAffiliationSelect(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleNoClick = () => {
|
|
||||||
onAffiliationSelect(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card sx={{ width: "100%", maxWidth: "100%" }}>
|
|
||||||
<CardContent>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
justifyContent: "center",
|
|
||||||
alignItems: "center",
|
|
||||||
height: "100%",
|
|
||||||
p: 2,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Title text={t("lbl_information_required")} size="h3" />
|
|
||||||
<Label htmlFor="loyalty" text= {t("msj_are_you_registered_as_affiliate")} />
|
|
||||||
<Box sx={{ mt: 3, display: "flex", gap: 3 }}>
|
|
||||||
<Grid size={{ xs: 6 }} sx={{ minWidth: "200px" }}>
|
|
||||||
<Button label={t("lbl_yes")} onClick={handleYesClick} variant="primary" className="h-16 w-full" />
|
|
||||||
</Grid>
|
|
||||||
<Grid size={{ xs: 6 }} sx={{ minWidth: "200px" }}>
|
|
||||||
<Button label={t('lbl_no')} onClick={handleNoClick} variant="primary" className="h-16 w-full" />
|
|
||||||
</Grid>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default AskForAffiliation
|
|
||||||
|
|
@ -1,111 +0,0 @@
|
||||||
"use client"
|
|
||||||
import { Box, Card, CardContent } from "@mui/material"
|
|
||||||
import Grid from "@mui/material/Grid2"
|
|
||||||
import React, { useEffect, useState } from "react"
|
|
||||||
import { useTranslation } from "react-i18next"
|
|
||||||
import Button from "@atoms/Button"
|
|
||||||
import Input from "@atoms/Input"
|
|
||||||
import Title from "@atoms/Title"
|
|
||||||
|
|
||||||
interface IdentityDocumentNumberProps {
|
|
||||||
documentType: string
|
|
||||||
onSubmit: (documentNumber: string) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
const IdentityDocumentNumber: React.FC<IdentityDocumentNumberProps> = ({ documentType, onSubmit }) => {
|
|
||||||
const [inputValue, setInputValue] = useState<string>("")
|
|
||||||
const [keyboardMode, setKeyboardMode] = useState<"numeric" | "alpha">("numeric")
|
|
||||||
const [isAlphaEnabled, setIsAlphaEnabled] = useState<boolean>(false)
|
|
||||||
const { t } = useTranslation()
|
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const alphaDocuments = ["PASAPORTE", "CURP", "RFC", "NIE", "RUT"]
|
|
||||||
setIsAlphaEnabled(alphaDocuments.includes(documentType.toUpperCase()))
|
|
||||||
setKeyboardMode(isAlphaEnabled ? "alpha" : "numeric")
|
|
||||||
}, [documentType, isAlphaEnabled])
|
|
||||||
|
|
||||||
const handleKeyPress = (key: string) => {
|
|
||||||
setInputValue((prev) => prev + key)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleDelete = () => {
|
|
||||||
setInputValue((prev) => prev.slice(0, -1))
|
|
||||||
}
|
|
||||||
|
|
||||||
const renderKeyboard = () => {
|
|
||||||
if (keyboardMode === "numeric") {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"].map((num) => (
|
|
||||||
<Grid key={num} size={{ xs: 4 }}>
|
|
||||||
<Button label={num} onClick={() => handleKeyPress(num)} variant="primary" className="h-16 w-full" />
|
|
||||||
</Grid>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{"ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("").map((letter) => (
|
|
||||||
<Grid key={letter} size={{ xs: 2 }}>
|
|
||||||
<Button label={letter} onClick={() => handleKeyPress(letter)} variant="primary" className="h-16 w-full" />
|
|
||||||
</Grid>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card sx={{ width: "100%", maxWidth: "100%" }}>
|
|
||||||
<CardContent>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
justifyContent: "center",
|
|
||||||
alignItems: "center",
|
|
||||||
height: "100%",
|
|
||||||
p: 2,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{/* Título */}
|
|
||||||
<Title text={`${t('msj_enter_number_of')} ${documentType}`} size="h4" />
|
|
||||||
|
|
||||||
<Input
|
|
||||||
type="text"
|
|
||||||
placeholder= {t('msj_enter_document_number')}
|
|
||||||
value={inputValue}
|
|
||||||
onChange={(e) => setInputValue(e.target.value)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Grid container spacing={2} justifyContent="center" alignItems="center" sx={{ mt: 3 }}>
|
|
||||||
<Grid size={{ xs: 4 }}>
|
|
||||||
<Button label= {t('lbl_delete')} onClick={handleDelete} variant="danger" className="h-16 w-full" />
|
|
||||||
</Grid>
|
|
||||||
<Grid size={{ xs: 4 }}>
|
|
||||||
<Button label={t('lbl_cancel')} onClick={() => setInputValue("")} variant="danger" className="h-16 w-full" />
|
|
||||||
</Grid>
|
|
||||||
{renderKeyboard()}
|
|
||||||
<Grid size={{ xs: 4 }}>
|
|
||||||
<Button label={t('lbl_enter')} onClick={() => onSubmit(inputValue)} variant="success" className="h-16 w-full" />
|
|
||||||
</Grid>
|
|
||||||
{isAlphaEnabled && (
|
|
||||||
<Grid size={{ xs: 4 }}>
|
|
||||||
<Button
|
|
||||||
label={`${t('msj_change_to')} ${keyboardMode === "numeric" ? t('lbl_alphabetical') : t('lbl_numeric')}`}
|
|
||||||
onClick={() => setKeyboardMode(keyboardMode === "numeric" ? "alpha" : "numeric")}
|
|
||||||
variant="info"
|
|
||||||
className="h-16 w-full"
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
)}
|
|
||||||
</Grid>
|
|
||||||
</Box>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default IdentityDocumentNumber
|
|
||||||
|
|
@ -1,27 +1,153 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Box, Container, Paper } from '@mui/material';
|
import { Box, Container, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, Paper } from '@mui/material';
|
||||||
import Grid from '@mui/material/Grid2';
|
import Grid from '@mui/material/Grid2';
|
||||||
import React from 'react';
|
import { useAtom } from 'jotai';
|
||||||
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import Button from '@atoms/Button';
|
||||||
|
import { storeNumberAtom, storePosAtom } from '@atoms/states/StoreAtom';
|
||||||
|
import { EndSaleOutputInterface } from '@interfaces/OutputInterfaces/Sale/EndSaleOutputInterface';
|
||||||
|
import { GetSaleOutputInterface } from '@interfaces/OutputInterfaces/Sale/GetSaleOutputInterface';
|
||||||
|
import { CustomerInterface } from '@interfaces/ResponseInterfaces/Customer/CustomerResponseInterface';
|
||||||
|
import { EndSaleResponseInterface } from '@interfaces/ResponseInterfaces/Sale/EndSaleResponseInterface';
|
||||||
|
import { GetSaleResponseInterface, SaleTransactionObjectInterface } from '@interfaces/ResponseInterfaces/Sale/GetSaleResponseInterface';
|
||||||
|
import { StartSaleResponseInterface } from '@interfaces/ResponseInterfaces/Sale/StartSaleResponseInterface';
|
||||||
|
import ClientSearch from '@organisms/Checkout/ClientSearch';
|
||||||
import ControlPanel from '@organisms/Checkout/ControlPanel';
|
import ControlPanel from '@organisms/Checkout/ControlPanel';
|
||||||
import ProductTable from '@organisms/Checkout/ProductTable';
|
import ProductTable from '@organisms/Checkout/ProductTable';
|
||||||
|
import EndSale from '@package/config/api/Sale/Transaction/EndSale/page';
|
||||||
|
import GetSale from '@package/config/api/Sale/Transaction/GetSale/page';
|
||||||
|
import { getUser } from '@utils/Utilities';
|
||||||
|
|
||||||
const products = [
|
interface MainLayoutProps {
|
||||||
{ id: 1, amount: 2, name: "Product X", unitValue: 2500, tax: 110, discount: 0, total: 5220 },
|
onEndTransaction: () => void; // Callback para notificar cuando la transacción termine
|
||||||
{ id: 2, amount: 1, name: "Product Y", unitValue: 3500, tax: 230, discount: 0, total: 3730 },
|
}
|
||||||
{ id: 3, amount: 3, name: "Product Z", unitValue: 1000, tax: 100, discount: 0, total: 3300 },
|
|
||||||
];
|
const MainLayout: React.FC<MainLayoutProps> = ({ onEndTransaction }) => {
|
||||||
|
const [transaction, setTransaction] = useState<StartSaleResponseInterface | null>(null);
|
||||||
|
const [client, setClient] = useState<CustomerInterface | null>(null);
|
||||||
|
const [productsList, setProductsList] = useState<SaleTransactionObjectInterface | null>(null);
|
||||||
|
const [endSaleResponse, setEndSaleResponse] = useState<EndSaleResponseInterface | null>(null);
|
||||||
|
const [openDialog, setOpenDialog] = useState(false);
|
||||||
|
|
||||||
|
const [idUser] = useState(getUser() && getUser().id ? getUser().id : '');
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const [storeNumber] = useAtom(storeNumberAtom);
|
||||||
|
const [storePos] = useAtom(storePosAtom);
|
||||||
|
|
||||||
|
const handleTransactionComplete = (transaction: StartSaleResponseInterface, client: CustomerInterface) => {
|
||||||
|
setTransaction(transaction);
|
||||||
|
setClient(client);
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchSaleDetails = useCallback(async () => {
|
||||||
|
if (!transaction || !client) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const saleRequest: GetSaleOutputInterface = {
|
||||||
|
idTransaction: transaction.object?.id,
|
||||||
|
storeNumber: storeNumber,
|
||||||
|
idSubsidiary: client.idSubsidiary,
|
||||||
|
idUser: client.idUser,
|
||||||
|
pos: storePos,
|
||||||
|
};
|
||||||
|
|
||||||
|
const saleResponse = await GetSale(saleRequest) as GetSaleResponseInterface;
|
||||||
|
|
||||||
|
if (saleResponse && saleResponse.correct) {
|
||||||
|
setProductsList(saleResponse.object);
|
||||||
|
} else {
|
||||||
|
console.error("Error fetching sale details: ", saleResponse?.message);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error during GetSale API call", error);
|
||||||
|
}
|
||||||
|
}, [client, storeNumber, storePos, transaction]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchSaleDetails();
|
||||||
|
}, [fetchSaleDetails, transaction]);
|
||||||
|
|
||||||
|
const handleProductAdded = () => {
|
||||||
|
fetchSaleDetails();
|
||||||
|
};
|
||||||
|
|
||||||
const MainLayout: React.FC = () => {
|
|
||||||
const handleVoidOrder = () => console.log("Void Order");
|
const handleVoidOrder = () => console.log("Void Order");
|
||||||
const handleVoidLast = () => console.log("Void Last");
|
const handleVoidLast = () => console.log("Void Last");
|
||||||
const handleResume = () => console.log("Resume");
|
const handleResume = () => console.log("Resume");
|
||||||
const handleSearchPLU = (plu: string) => console.log("Search PLU:", plu);
|
|
||||||
|
|
||||||
const subtotal = products.reduce((acc, product) => acc + product.unitValue * product.amount, 0);
|
const handleEndSale = async () => {
|
||||||
const discount = products.reduce((acc, product) => acc + product.discount, 0);
|
if (!transaction || !client || !productsList) return;
|
||||||
const tax = products.reduce((acc, product) => acc + product.tax, 0);
|
|
||||||
const total = products.reduce((acc, product) => acc + product.total, 0);
|
if (!transaction.object?.id) {
|
||||||
|
console.error("Transaction ID is missing");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (client.idSubsidiary === undefined) {
|
||||||
|
console.error("Client subsidiary ID is missing");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const storeAddress = process.env.NEXT_PUBLIC_STORE_ADDRESS || '';
|
||||||
|
const codeMunPos = process.env.NEXT_PUBLIC_STORE_CODE_CITY || '';
|
||||||
|
const nameMunPos = process.env.NEXT_PUBLIC_STORE_CITY || '';
|
||||||
|
const codeDepPos = process.env.NEXT_PUBLIC_STORE_CODE_DEPARTMENT || '';
|
||||||
|
const nameDepPos = process.env.NEXT_PUBLIC_STORE_DEPARTMENT || '';
|
||||||
|
|
||||||
|
if (!storeAddress || !codeMunPos || !nameMunPos || !codeDepPos || !nameDepPos) {
|
||||||
|
console.error("Missing store address information from environment variables");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const endSaleRequest: EndSaleOutputInterface = {
|
||||||
|
idTransaction: transaction.object.id,
|
||||||
|
storeNumber: storeNumber,
|
||||||
|
idSubsidiary: client.idSubsidiary,
|
||||||
|
idUser: idUser,
|
||||||
|
localDate: new Date().toISOString(),
|
||||||
|
pos: storePos,
|
||||||
|
comment: 'Test End Sale',
|
||||||
|
payment: [
|
||||||
|
{
|
||||||
|
type: "CASH",
|
||||||
|
approvalCode: "0001",
|
||||||
|
receiptNumber: "10001",
|
||||||
|
value: productsList.totalWithPromotion+ productsList.totalTaxes,
|
||||||
|
cardNumber: "0032",
|
||||||
|
cardType: "DEBIT",
|
||||||
|
comment: "Test"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
infoPos: {
|
||||||
|
addressPos: storeAddress,
|
||||||
|
codeMunPos: codeMunPos,
|
||||||
|
nameMunPos: nameMunPos,
|
||||||
|
codeDepPos: codeDepPos,
|
||||||
|
nameDepPos: nameDepPos,
|
||||||
|
pos: storePos
|
||||||
|
}
|
||||||
|
};
|
||||||
|
console.log("End Sale Request: ", endSaleRequest);
|
||||||
|
try {
|
||||||
|
const endSaleResponse = await EndSale(endSaleRequest) as EndSaleResponseInterface;
|
||||||
|
|
||||||
|
console.log("End Sale Response: ", endSaleResponse);
|
||||||
|
if (endSaleResponse && endSaleResponse.correct) {
|
||||||
|
setEndSaleResponse(endSaleResponse);
|
||||||
|
setOpenDialog(true);
|
||||||
|
} else {
|
||||||
|
console.error("Error ending sale:", endSaleResponse?.message);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error during EndSale API call", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDialogClose = () => {
|
||||||
|
setOpenDialog(false);
|
||||||
|
onEndTransaction();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', bgcolor: 'background.default', boxShadow: '4' }}>
|
<Box sx={{ display: 'flex', flexDirection: 'column', bgcolor: 'background.default', boxShadow: '4' }}>
|
||||||
|
|
@ -29,25 +155,51 @@ const MainLayout: React.FC = () => {
|
||||||
<Grid container spacing={2}>
|
<Grid container spacing={2}>
|
||||||
<Grid size={{ xs: 12, md: 8 }}>
|
<Grid size={{ xs: 12, md: 8 }}>
|
||||||
<Paper elevation={3} sx={{ height: '100%', p: 2 }}>
|
<Paper elevation={3} sx={{ height: '100%', p: 2 }}>
|
||||||
<ProductTable products={products} />
|
<ProductTable products={productsList} />
|
||||||
|
|
||||||
</Paper>
|
</Paper>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid size={{ xs: 12, md: 4 }}>
|
<Grid size={{ xs: 12, md: 4 }}>
|
||||||
<Paper elevation={3} sx={{ height: '100%', p: 2 }}>
|
<Paper elevation={3} sx={{ height: '100%', p: 2 }}>
|
||||||
|
{!transaction && !client ? (
|
||||||
|
<ClientSearch onTransactionComplete={handleTransactionComplete} />
|
||||||
|
) : (
|
||||||
<ControlPanel
|
<ControlPanel
|
||||||
onVoidOrder={handleVoidOrder}
|
onVoidOrder={handleVoidOrder}
|
||||||
onVoidLast={handleVoidLast}
|
onVoidLast={handleVoidLast}
|
||||||
onResume={handleResume}
|
onResume={handleResume}
|
||||||
onSearchPLU={handleSearchPLU}
|
transaction={transaction!}
|
||||||
subtotal={subtotal}
|
client={client!}
|
||||||
discount={discount}
|
onProductAdded={handleProductAdded}
|
||||||
tax={tax}
|
|
||||||
total={total}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
)}
|
||||||
|
{productsList && productsList.list.length > 0 && (
|
||||||
|
<Button
|
||||||
|
label={t("lbl_resume")}
|
||||||
|
variant="primary"
|
||||||
|
onClick={handleEndSale}
|
||||||
|
className="full-width-button"
|
||||||
|
disabled={!productsList || productsList.list.length === 0}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
</Paper>
|
</Paper>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Container>
|
</Container>
|
||||||
|
|
||||||
|
<Dialog open={openDialog} onClose={handleDialogClose}>
|
||||||
|
<DialogTitle>{t("transaction_complete")}</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogContentText>
|
||||||
|
{endSaleResponse?.object?.description || t("transaction_successful")}
|
||||||
|
</DialogContentText>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={handleDialogClose} label="Aceptar" />
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
"use client"
|
|
||||||
import { Box, Card, CardContent } from "@mui/material"
|
|
||||||
import Grid from "@mui/material/Grid2"
|
|
||||||
import { useTranslation } from "react-i18next"
|
|
||||||
import Button from "@atoms/Button"
|
|
||||||
import Label from "@atoms/Label"
|
|
||||||
import Title from "@atoms/Title"
|
|
||||||
|
|
||||||
interface TypesDocumentProps {
|
|
||||||
typesDocument: string[]
|
|
||||||
onDocumentSelect: (documentType: string) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
const TypeIdentityDocument: React.FC<TypesDocumentProps> = ({ typesDocument, onDocumentSelect }) => {
|
|
||||||
const { t } = useTranslation()
|
|
||||||
|
|
||||||
const handleDocumentClick = (documentType: string) => {
|
|
||||||
onDocumentSelect(documentType)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card sx={{ width: "100%", maxWidth: "100%" }}>
|
|
||||||
<CardContent>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
justifyContent: "center",
|
|
||||||
alignItems: "center",
|
|
||||||
height: "100%",
|
|
||||||
p: 1,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Title text= {t("lbl_information_required")} size="h4" />
|
|
||||||
<Label htmlFor="loyalty" text={t('msj_select_type_identity_document')} />
|
|
||||||
|
|
||||||
<Grid container spacing={1} justifyContent="center" sx={{ mt: 2 }}>
|
|
||||||
{typesDocument.map((documentType, index) => (
|
|
||||||
<Grid key={index} size={{ xs: 4, md: 6 }} sx={{ minWidth: "100px" }}>
|
|
||||||
<Button
|
|
||||||
label={documentType}
|
|
||||||
onClick={() => handleDocumentClick(documentType)}
|
|
||||||
variant="primary"
|
|
||||||
className="h-16 w-full"
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
))}
|
|
||||||
</Grid>
|
|
||||||
</Box>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default TypeIdentityDocument
|
|
||||||
|
|
@ -5,15 +5,14 @@ import Grid from '@mui/material/Grid2';
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { ItemObject } from '@interfaces/ResponseInterfaces/Sale/AddProductsResponseInterface';
|
||||||
|
|
||||||
interface SaleSummaryProps {
|
interface SaleSummaryProps {
|
||||||
subtotal: number;
|
product: ItemObject;
|
||||||
discount: number;
|
|
||||||
tax: number;
|
|
||||||
total: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const SaleSummary: React.FC<SaleSummaryProps> = ({ subtotal, discount, tax, total }) => {
|
const SaleSummary: React.FC<SaleSummaryProps> = ({ product }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -30,15 +29,13 @@ const SaleSummary: React.FC<SaleSummaryProps> = ({ subtotal, discount, tax, tota
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell sx={{ color: 'primary', borderColor: 'black', fontWeight: 'bold', width: '30%' }}>{t("tle_product")}</TableCell>
|
<TableCell sx={{ color: 'primary', borderColor: 'black', fontWeight: 'bold', width: '30%' }}>{t("tle_product")}</TableCell>
|
||||||
<TableCell sx={{ color: 'primary', borderColor: 'black', fontWeight: 'bold', width: '50%' }}>{t("tle_description")}</TableCell>
|
|
||||||
<TableCell sx={{ color: 'primary', borderColor: 'black', fontWeight: 'bold', width: '20%', textAlign: 'right' }}>{t("tle_amount")}</TableCell>
|
<TableCell sx={{ color: 'primary', borderColor: 'black', fontWeight: 'bold', width: '20%', textAlign: 'right' }}>{t("tle_amount")}</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell sx={{ color: 'black', borderColor: 'black' }}>Product R</TableCell>
|
<TableCell sx={{ color: 'black', borderColor: 'black' }}>{product.name}</TableCell>
|
||||||
<TableCell sx={{ color: 'black', borderColor: 'black' }}>Producto para r uso</TableCell>
|
<TableCell sx={{ color: 'black', borderColor: 'black', textAlign: 'right' }}>1</TableCell>
|
||||||
<TableCell sx={{ color: 'black', borderColor: 'black', textAlign: 'right' }}>3</TableCell>
|
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
|
|
@ -49,31 +46,16 @@ const SaleSummary: React.FC<SaleSummaryProps> = ({ subtotal, discount, tax, tota
|
||||||
<Typography variant="body2" align="right">{t("lbl_subtotal")}:</Typography>
|
<Typography variant="body2" align="right">{t("lbl_subtotal")}:</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid size={{xs:5}}>
|
<Grid size={{xs:5}}>
|
||||||
<Typography variant="body2" align="left">$ 1.400</Typography>
|
<Typography variant="body2" align="left">{product.regularUnitPrice}</Typography>
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
<Grid container>
|
|
||||||
<Grid size={{xs:7}} sx={{ pr: 10 }}>
|
|
||||||
<Typography variant="body2" align="right">{t("lbl_discount")}:</Typography>
|
|
||||||
</Grid>
|
|
||||||
<Grid size={{xs:5}}>
|
|
||||||
<Typography variant="body2" align="left">$ 12</Typography>
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Grid container>
|
<Grid container>
|
||||||
<Grid size={{xs:7}} sx={{ pr: 5.5 }}>
|
<Grid size={{xs:7}} sx={{ pr: 5.5 }}>
|
||||||
<Typography variant="body2" align="right">{t("lbl_tax")}:</Typography>
|
<Typography variant="body2" align="right">{t("lbl_tax")}:</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid size={{xs:5}}>
|
<Grid size={{xs:5}}>
|
||||||
<Typography variant="body2" align="left">$ 123</Typography>
|
<Typography variant="body2" align="left">{product.totalTaxes}</Typography>
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
<Grid container>
|
|
||||||
<Grid size={{xs:7}} sx={{ pr: 5.5 }}>
|
|
||||||
<Typography variant="body2" align="right">{t("lbl_total")}:</Typography>
|
|
||||||
</Grid>
|
|
||||||
<Grid size={{xs:5}}>
|
|
||||||
<Typography variant="body2" align="left">$ 1.511</Typography>
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
@ -82,7 +64,7 @@ const SaleSummary: React.FC<SaleSummaryProps> = ({ subtotal, discount, tax, tota
|
||||||
<Box sx={{ p: 3, boxShadow: '2' }}>
|
<Box sx={{ p: 3, boxShadow: '2' }}>
|
||||||
<Typography variant="h6" sx={{ color: 'primary', display: 'flex', justifyContent: 'space-between' }}>
|
<Typography variant="h6" sx={{ color: 'primary', display: 'flex', justifyContent: 'space-between' }}>
|
||||||
<span>{t("tle_total")}:</span>
|
<span>{t("tle_total")}:</span>
|
||||||
<span>$ 12.250</span>
|
<span>{product.extendedPrice}</span>
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,10 @@ import Typography from '@mui/material/Typography';
|
||||||
import { useAtom } from 'jotai';
|
import { useAtom } from 'jotai';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import UserAtom from '@atoms/states/UserAtom';
|
import CustomerAtom from '@atoms/states/CustomerAtom';
|
||||||
|
|
||||||
const UserHeader: React.FC = () => {
|
const CustomerHeader: React.FC = () => {
|
||||||
const [user] = useAtom(UserAtom);
|
const [customer] = useAtom(CustomerAtom);
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -23,19 +23,19 @@ const UserHeader: React.FC = () => {
|
||||||
>
|
>
|
||||||
<Grid2>
|
<Grid2>
|
||||||
<Typography variant="h6" className="font-bold">
|
<Typography variant="h6" className="font-bold">
|
||||||
{user ? `${t("lbl_name_customer")}: ${user.name} ${user.lastName}` : `${t("lbl_name_customer")}: Anónimo`}
|
{customer ? `${t("lbl_name_customer")}: ${customer.name} ${customer.firstSurname}` : `${t("lbl_name_customer")}: Anónimo`}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid2>
|
</Grid2>
|
||||||
|
|
||||||
<Grid2>
|
<Grid2>
|
||||||
<Typography variant="body2" color="textSecondary" className="text-white">
|
<Typography variant="body2" color="textSecondary" className="text-white">
|
||||||
{user ? `${t("lbl_dni_customer")}: ${user.idTypeUser}` : `${t("lbl_dni_customer")}: 222222222222`}
|
{customer ? `${t("lbl_dni_customer")}: ${customer.dni}` : `${t("lbl_dni_customer")}: 222222222222`}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid2>
|
</Grid2>
|
||||||
|
|
||||||
<Grid2>
|
<Grid2>
|
||||||
<Typography variant="body2" className="text-white">
|
<Typography variant="body2" className="text-white">
|
||||||
{user ? `${t("lbl_email_customer")}: ${user.email}` : `${t("lbl_email_customer")}: generic@example.com`}
|
{customer ? `${t("lbl_email_customer")}: ${customer.mail}` : `${t("lbl_email_customer")}: generic@example.com`}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid2>
|
</Grid2>
|
||||||
</Grid2>
|
</Grid2>
|
||||||
|
|
@ -43,4 +43,4 @@ const UserHeader: React.FC = () => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default UserHeader;
|
export default CustomerHeader;
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,29 @@
|
||||||
'use client';
|
'use client';
|
||||||
import { Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, Paper, Typography } from '@mui/material';
|
import { Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, Paper, Typography } from '@mui/material';
|
||||||
import Grid from '@mui/material/Grid2';
|
import Grid from '@mui/material/Grid2';
|
||||||
import { useSetAtom } from 'jotai';
|
import { useAtom } from 'jotai';
|
||||||
import { useRouter } from 'next/navigation';
|
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import Button from '@atoms/Button';
|
import Button from '@atoms/Button';
|
||||||
import CustomLoading from '@atoms/CustomLoading';
|
import CustomLoading from '@atoms/CustomLoading';
|
||||||
import Icon from '@atoms/Icon';
|
import Icon from '@atoms/Icon';
|
||||||
import Input from '@atoms/Input';
|
import Input from '@atoms/Input';
|
||||||
import Label from '@atoms/Label';
|
import Label from '@atoms/Label';
|
||||||
import CustomerAtom from '@atoms/states/CustomerAtom';
|
import { storeNumberAtom, storePosAtom } from '@atoms/states/StoreAtom';
|
||||||
import Title from '@atoms/Title';
|
import Title from '@atoms/Title';
|
||||||
import { Tooltip } from '@atoms/Tooltip';
|
import { Tooltip } from '@atoms/Tooltip';
|
||||||
import { useClientSearchLogic } from '@hooks/useClientSearchLogic';
|
import { useClientSearchLogic } from '@hooks/useClientSearchLogic';
|
||||||
|
import { CustomerInterface } from '@interfaces/ResponseInterfaces/Customer/CustomerResponseInterface';
|
||||||
import { CustomerDniTypeInterface } from '@interfaces/ResponseInterfaces/Customer/DNITypeResponseInterface';
|
import { CustomerDniTypeInterface } from '@interfaces/ResponseInterfaces/Customer/DNITypeResponseInterface';
|
||||||
|
import { StartSaleResponseInterface } from '@interfaces/ResponseInterfaces/Sale/StartSaleResponseInterface';
|
||||||
|
import StartSale from '@package/config/api/Sale/Transaction/StartSale/page';
|
||||||
|
import { getUser } from '@utils/Utilities';
|
||||||
|
|
||||||
const ClientSearch: React.FC = () => {
|
interface ClientSearchProps {
|
||||||
|
onTransactionComplete: (transaction: StartSaleResponseInterface, client: CustomerInterface) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ClientSearch: React.FC<ClientSearchProps> = ({ onTransactionComplete }) => {
|
||||||
const {
|
const {
|
||||||
idTypes,
|
idTypes,
|
||||||
loading,
|
loading,
|
||||||
|
|
@ -27,11 +33,15 @@ const ClientSearch: React.FC = () => {
|
||||||
handleInputChange,
|
handleInputChange,
|
||||||
handleSubmitForm,
|
handleSubmitForm,
|
||||||
isAffiliated,
|
isAffiliated,
|
||||||
|
customer,
|
||||||
} = useClientSearchLogic();
|
} = useClientSearchLogic();
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [openDialog, setOpenDialog] = useState(false);
|
const [openDialog, setOpenDialog] = useState(false);
|
||||||
const setCustomer = useSetAtom(CustomerAtom);
|
const [idUser] = useState(getUser() && getUser().id ? getUser().id : '');
|
||||||
const router = useRouter();
|
|
||||||
|
const [storeNumber] = useAtom(storeNumberAtom);
|
||||||
|
const [storePos] = useAtom(storePosAtom);
|
||||||
|
|
||||||
const handleOpenDialog = () => {
|
const handleOpenDialog = () => {
|
||||||
setOpenDialog(true);
|
setOpenDialog(true);
|
||||||
|
|
@ -41,20 +51,20 @@ const ClientSearch: React.FC = () => {
|
||||||
setOpenDialog(false);
|
setOpenDialog(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUseGenericCustomer = () => {
|
const handleUseGenericCustomer = async () => {
|
||||||
setCustomer({
|
const genericCustomer = {
|
||||||
id: '',
|
id: '',
|
||||||
idCompany: 'default-company',
|
idCompany: 'default-company',
|
||||||
companyName: 'default-company-name',
|
companyName: 'default-company-name',
|
||||||
idSubsidiary: 0,
|
idSubsidiary: 3,
|
||||||
nameSubsidiary: 'default-subsidiary',
|
nameSubsidiary: 'default-subsidiary',
|
||||||
externalClientId: null,
|
externalClientId: null,
|
||||||
name: 'Cliente',
|
name: 'Cliente',
|
||||||
secondName: '',
|
secondName: '',
|
||||||
firstSurname: 'Generico',
|
firstSurname: 'Generico',
|
||||||
secondSurname: '',
|
secondSurname: '',
|
||||||
idTypeCustomer: 'Tarjeta de identidad',
|
idTypeCustomer: '12',
|
||||||
nameTypeCustomer: 'Tarjeta',
|
nameTypeCustomer: 'Tarjeta de identidad',
|
||||||
dni: '222222222222',
|
dni: '222222222222',
|
||||||
idGender: '',
|
idGender: '',
|
||||||
nameGender: '',
|
nameGender: '',
|
||||||
|
|
@ -74,14 +84,75 @@ const ClientSearch: React.FC = () => {
|
||||||
group: [],
|
group: [],
|
||||||
NameSurname: '',
|
NameSurname: '',
|
||||||
SurnameName: ''
|
SurnameName: ''
|
||||||
});
|
};
|
||||||
|
|
||||||
router.push("/selfcheckout/checkout");
|
const saleData = {
|
||||||
|
storeNumber: storeNumber,
|
||||||
|
idSubsidiary: genericCustomer.idSubsidiary,
|
||||||
|
idClient: genericCustomer.id,
|
||||||
|
clientDni: genericCustomer.dni,
|
||||||
|
idType: genericCustomer.idTypeCustomer,
|
||||||
|
idGroup: '',
|
||||||
|
invoiceName: genericCustomer.name,
|
||||||
|
invoiceTypeNit: genericCustomer.idTypeCustomer,
|
||||||
|
invoiceNit: genericCustomer.dni,
|
||||||
|
invoiceDirection: genericCustomer.address,
|
||||||
|
invoiceCellPhone: genericCustomer.phone,
|
||||||
|
invoiceEmail: genericCustomer.mail,
|
||||||
|
idUser: idUser,
|
||||||
|
localDate: new Date().toISOString(),
|
||||||
|
pos: storePos,
|
||||||
|
};
|
||||||
|
|
||||||
|
const saleResponse = await StartSale(saleData) as StartSaleResponseInterface;
|
||||||
|
|
||||||
|
if (saleResponse && saleResponse.correct) {
|
||||||
|
if (customer) {
|
||||||
|
onTransactionComplete(saleResponse, customer);
|
||||||
|
} else {
|
||||||
|
console.error("Customer is null");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error(saleResponse.message);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = (e: React.FormEvent) => {
|
const handleSubmit = (e: React.FormEvent) => {
|
||||||
e.preventDefault(); // Evita el comportamiento por defecto del formulario
|
e.preventDefault();
|
||||||
handleSubmitForm(); // Ejecuta la función de búsqueda con los datos actuales
|
handleSubmitForm();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleContinue = async () => {
|
||||||
|
if (customer) {
|
||||||
|
const saleData = {
|
||||||
|
storeNumber: storeNumber,
|
||||||
|
idSubsidiary: customer.idSubsidiary || 0,
|
||||||
|
idClient: customer.id,
|
||||||
|
clientDni: customer.dni,
|
||||||
|
idType: customer.idTypeCustomer,
|
||||||
|
idGroup: '',
|
||||||
|
invoiceName: customer.name,
|
||||||
|
invoiceTypeNit: customer.idTypeCustomer,
|
||||||
|
invoiceNit: customer.dni,
|
||||||
|
invoiceDirection: customer.address,
|
||||||
|
invoiceCellPhone: customer.phone,
|
||||||
|
invoiceEmail: customer.mail,
|
||||||
|
idUser: idUser,
|
||||||
|
localDate: new Date().toISOString(),
|
||||||
|
pos: storePos,
|
||||||
|
};
|
||||||
|
console.log("VALOR DE SALEDATA",saleData);
|
||||||
|
const saleResponse = await StartSale(saleData) as StartSaleResponseInterface;
|
||||||
|
|
||||||
|
console.log("VALOR DE SALESRESPONSE",saleResponse);
|
||||||
|
if (saleResponse && saleResponse.correct) {
|
||||||
|
onTransactionComplete(saleResponse, customer);
|
||||||
|
} else {
|
||||||
|
console.error(saleResponse.message);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
handleOpenDialog();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -95,7 +166,7 @@ const ClientSearch: React.FC = () => {
|
||||||
<select
|
<select
|
||||||
name="idType"
|
name="idType"
|
||||||
value={formData.idType}
|
value={formData.idType}
|
||||||
onChange={handleInputChange} // Asegura que el estado del input cambie
|
onChange={handleInputChange}
|
||||||
className="w-full p-2 border rounded"
|
className="w-full p-2 border rounded"
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
>
|
>
|
||||||
|
|
@ -119,7 +190,7 @@ const ClientSearch: React.FC = () => {
|
||||||
name="idNumber"
|
name="idNumber"
|
||||||
placeholder={t("msj_enter_id_number")}
|
placeholder={t("msj_enter_id_number")}
|
||||||
value={formData.idNumber}
|
value={formData.idNumber}
|
||||||
onChange={handleInputChange} // Actualiza el valor del input al escribir
|
onChange={handleInputChange}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
@ -166,7 +237,6 @@ const ClientSearch: React.FC = () => {
|
||||||
label={t("lbl_create_new_customer")}
|
label={t("lbl_create_new_customer")}
|
||||||
variant="primary"
|
variant="primary"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
// Lógica para crear nuevo cliente
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -209,9 +279,7 @@ const ClientSearch: React.FC = () => {
|
||||||
<Button
|
<Button
|
||||||
label={t("lbl_continue")}
|
label={t("lbl_continue")}
|
||||||
variant="primary"
|
variant="primary"
|
||||||
onClick={() => {
|
onClick={handleContinue}
|
||||||
router.push("/selfcheckout/checkout");
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
@ -1,32 +1,78 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import Grid2 from '@mui/material/Grid2';
|
import Grid2 from '@mui/material/Grid2';
|
||||||
import React from 'react';
|
import { useAtom } from 'jotai';
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { storeNumberAtom, storePosAtom } from '@atoms/states/StoreAtom';
|
||||||
|
import { AddProductsOutputInterface } from '@interfaces/OutputInterfaces/Sale/AddProductsOutputInterfae';
|
||||||
|
import { CustomerInterface } from '@interfaces/ResponseInterfaces/Customer/CustomerResponseInterface';
|
||||||
|
import { AddProductsResponseInterface, ItemObject } from '@interfaces/ResponseInterfaces/Sale/AddProductsResponseInterface';
|
||||||
|
import { StartSaleResponseInterface } from '@interfaces/ResponseInterfaces/Sale/StartSaleResponseInterface';
|
||||||
import ActionButtons from '@molecules/ActionButtons';
|
import ActionButtons from '@molecules/ActionButtons';
|
||||||
import ProductSearch from '@molecules/ProductSearch';
|
import ProductSearch from '@molecules/ProductSearch';
|
||||||
import SaleSummary from '@molecules/SaleSummary';
|
import SaleSummary from '@molecules/SaleSummary';
|
||||||
|
import AddProducts from "@package/config/api/Sale/Transaction/AddProducts/page";
|
||||||
|
|
||||||
interface ControlPanelProps {
|
interface ControlPanelProps {
|
||||||
onVoidOrder: () => void;
|
onVoidOrder: () => void;
|
||||||
onVoidLast: () => void;
|
onVoidLast: () => void;
|
||||||
onResume: () => void;
|
onResume: () => void;
|
||||||
onSearchPLU: (plu: string) => void;
|
transaction: StartSaleResponseInterface;
|
||||||
subtotal: number;
|
client: CustomerInterface;
|
||||||
discount: number;
|
onProductAdded: () => void;
|
||||||
tax: number;
|
|
||||||
total: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ControlPanel: React.FC<ControlPanelProps> = ({
|
const ControlPanel: React.FC<ControlPanelProps> = ({
|
||||||
onVoidOrder,
|
onVoidOrder,
|
||||||
onVoidLast,
|
onVoidLast,
|
||||||
onResume,
|
onResume,
|
||||||
onSearchPLU,
|
transaction,
|
||||||
subtotal,
|
client,
|
||||||
discount,
|
onProductAdded,
|
||||||
tax,
|
|
||||||
total
|
|
||||||
}) => {
|
}) => {
|
||||||
|
|
||||||
|
const [addedProducts, setAddedProducts] = useState<ItemObject>({} as ItemObject);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [storeNumber] = useAtom(storeNumberAtom);
|
||||||
|
const [storePos] = useAtom(storePosAtom);
|
||||||
|
|
||||||
|
|
||||||
|
const handleSearchSKU = async (sku: string) => {
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const addProductRequest: AddProductsOutputInterface = {
|
||||||
|
idTransaction: transaction.object?.id,
|
||||||
|
storeNumber: storeNumber,
|
||||||
|
idSubsidiary: client.idSubsidiary,
|
||||||
|
clientNit: client.dni,
|
||||||
|
idUser: client.idUser,
|
||||||
|
localDate: new Date(),
|
||||||
|
pos: storePos,
|
||||||
|
products: [
|
||||||
|
{
|
||||||
|
sku: sku,
|
||||||
|
value: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const productResponse = await AddProducts(addProductRequest) as AddProductsResponseInterface;
|
||||||
|
|
||||||
|
if (productResponse && productResponse.correct) {
|
||||||
|
const newProduct = productResponse.object;
|
||||||
|
setAddedProducts(newProduct)
|
||||||
|
onProductAdded();
|
||||||
|
} else {
|
||||||
|
console.error("Error adding product: ", productResponse?.message);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error during AddProducts API call", error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid2 container spacing={4}>
|
<Grid2 container spacing={4}>
|
||||||
<Grid2 size={{xs:12}}>
|
<Grid2 size={{xs:12}}>
|
||||||
|
|
@ -34,11 +80,11 @@ const ControlPanel: React.FC<ControlPanelProps> = ({
|
||||||
</Grid2>
|
</Grid2>
|
||||||
|
|
||||||
<Grid2 size={{xs:12}}>
|
<Grid2 size={{xs:12}}>
|
||||||
<ProductSearch onSearch={onSearchPLU} />
|
<ProductSearch onSearch={handleSearchSKU} />
|
||||||
</Grid2>
|
</Grid2>
|
||||||
|
|
||||||
<Grid2 size={{xs:12}}>
|
<Grid2 size={{xs:12}}>
|
||||||
<SaleSummary subtotal={subtotal} discount={discount} tax={tax} total={total} />
|
<SaleSummary product={addedProducts} />
|
||||||
</Grid2>
|
</Grid2>
|
||||||
</Grid2>
|
</Grid2>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -3,30 +3,16 @@
|
||||||
import { Box, Table, TableBody, TableCell, TableHead, TableRow } from '@mui/material';
|
import { Box, Table, TableBody, TableCell, TableHead, TableRow } from '@mui/material';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { SaleTransactionObjectInterface } from '@interfaces/ResponseInterfaces/Sale/GetSaleResponseInterface';
|
||||||
import CustomCard from '@molecules/CustomCard';
|
import CustomCard from '@molecules/CustomCard';
|
||||||
import UserHeader from '@molecules/UserHeader';
|
import UserHeader from '@molecules/UserHeader';
|
||||||
|
|
||||||
interface Product {
|
|
||||||
id: number;
|
|
||||||
amount: number;
|
|
||||||
name: string;
|
|
||||||
unitValue: number;
|
|
||||||
tax: number;
|
|
||||||
discount: number;
|
|
||||||
total: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ProductTableProps {
|
interface ProductTableProps {
|
||||||
products: Product[];
|
products: SaleTransactionObjectInterface | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ProductTable: React.FC<ProductTableProps> = ({ products }) => {
|
const ProductTable: React.FC<ProductTableProps> = ({ products }) => {
|
||||||
const totalAmount = products.reduce((acc, product) => acc + product.amount, 0);
|
const { t } = useTranslation();
|
||||||
const totalUnitValue = products.reduce((acc, product) => acc + product.unitValue * product.amount, 0);
|
|
||||||
const totalTax = products.reduce((acc, product) => acc + product.tax, 0);
|
|
||||||
const totalDiscount = products.reduce((acc, product) => acc + product.discount, 0);
|
|
||||||
const grandTotal = products.reduce((acc, product) => acc + product.total, 0);
|
|
||||||
const { t } = useTranslation()
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
<Box sx={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
||||||
|
|
@ -48,38 +34,60 @@ const ProductTable: React.FC<ProductTableProps> = ({ products }) => {
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{products.map((product) => (
|
{products?.list?.length ? (
|
||||||
<TableRow key={product.id}>
|
products.list.map((product) => (
|
||||||
<TableCell>{product.amount}</TableCell>
|
<TableRow key={product.idDetail}>
|
||||||
|
<TableCell>{product.quantity}</TableCell>
|
||||||
<TableCell>{product.name}</TableCell>
|
<TableCell>{product.name}</TableCell>
|
||||||
<TableCell>${product.unitValue.toFixed(2)}</TableCell>
|
<TableCell>${product.regularUnitPrice.toFixed(2)}</TableCell>
|
||||||
<TableCell>${product.tax.toFixed(2)}</TableCell>
|
<TableCell>${product.totalTaxes.toFixed(2)}</TableCell>
|
||||||
<TableCell>${product.discount.toFixed(2)}</TableCell>
|
<TableCell>${product.discount.toFixed(2)}</TableCell>
|
||||||
<TableCell>${product.total.toFixed(2)}</TableCell>
|
<TableCell>${product.extendedPrice.toFixed(2)}</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))
|
||||||
|
) : (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell colSpan={6} align="center">
|
||||||
|
{t("msj_no_products_added")} {/* Mensaje de no productos */}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
)}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
</Box>
|
</Box>
|
||||||
<Box sx={{ p:0.2, boxShadow: '2', }}>
|
<Box sx={{ p: 0.2, boxShadow: '2' }}>
|
||||||
<CustomCard className="bg-gray-700 text-white">
|
<CustomCard className="bg-gray-700 text-white">
|
||||||
<Table>
|
<Table>
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell sx={{ color: 'RGB(255,255,255)', borderColor: 'with', fontWeight: 'bold', width: '20%' }}>{t("tle_item_count")}</TableCell>
|
<TableCell sx={{ color: 'RGB(255,255,255)', fontWeight: 'bold', width: '20%' }}>
|
||||||
<TableCell sx={{ color: 'RGB(255,255,255)', borderColor: 'with', fontWeight: 'bold', width: '20%' }}>{t("tle_total_unit")}</TableCell>
|
{t("tle_total_unit")}
|
||||||
<TableCell sx={{ color: 'RGB(255,255,255)', borderColor: 'with', fontWeight: 'bold', width: '20%' }}>{t("tle_total_taxes")}</TableCell>
|
</TableCell>
|
||||||
<TableCell sx={{ color: 'RGB(255,255,255)', borderColor: 'with', fontWeight: 'bold', width: '20%' }}>{t("tle_total_discount")}</TableCell>
|
<TableCell sx={{ color: 'RGB(255,255,255)', fontWeight: 'bold', width: '20%' }}>
|
||||||
<TableCell sx={{ color: 'RGB(255,255,255)', borderColor: 'with', fontWeight: 'bold', width: '20%' }}>{t("tle_total")}</TableCell>
|
{t("tle_total_taxes")}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell sx={{ color: 'RGB(255,255,255)', fontWeight: 'bold', width: '20%' }}>
|
||||||
|
{t("tle_total_discount")}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell sx={{ color: 'RGB(255,255,255)', fontWeight: 'bold', width: '20%' }}>
|
||||||
|
{t("tle_total")}
|
||||||
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell sx={{ color: 'RGB(255,255,255)', borderColor: 'with' }}>{totalAmount}</TableCell>
|
<TableCell sx={{ color: 'RGB(255,255,255)' }}>
|
||||||
<TableCell sx={{ color: 'RGB(255,255,255)', borderColor: 'with' }}>${totalUnitValue.toFixed(2)}</TableCell>
|
${products?.totalSale?.toFixed(2) || '0.00'}
|
||||||
<TableCell sx={{ color: 'RGB(255,255,255)', borderColor: 'with' }}>${totalTax.toFixed(2)}</TableCell>
|
</TableCell>
|
||||||
<TableCell sx={{ color: 'RGB(255,255,255)', borderColor: 'with' }}>${totalDiscount.toFixed(2)}</TableCell>
|
<TableCell sx={{ color: 'RGB(255,255,255)' }}>
|
||||||
<TableCell sx={{ color: 'RGB(255,255,255)', borderColor: 'with' }}>${grandTotal.toFixed(2)}</TableCell>
|
${products?.totalTaxes?.toFixed(2) || '0.00'}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell sx={{ color: 'RGB(255,255,255)' }}>
|
||||||
|
$0.00 {/* Descuento total si aplica */}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell sx={{ color: 'RGB(255,255,255)' }}>
|
||||||
|
${((products?.totalWithPromotion ?? 0) + (products?.totalTaxes ?? 0)).toFixed(2)}
|
||||||
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { GetTypeDNIOutputInterface } from "@interfaces/OutputInterfaces/Customer/GetTypeDNIOutputInterface";
|
import { GetTypeDNIOutputInterface } from "@interfaces/OutputInterfaces/Customer/GetTypeDNIOutputInterface";
|
||||||
|
import { CustomerDniTypeResponseInterface } from "@interfaces/ResponseInterfaces/Customer/DNITypeResponseInterface";
|
||||||
import { BASE_URL_CUSTOMER_TYPEDNI } from "@package/config/ApiPath";
|
import { BASE_URL_CUSTOMER_TYPEDNI } from "@package/config/ApiPath";
|
||||||
import { FetchData } from "@package/config/FetchData";
|
import { FetchData } from "@package/config/FetchData";
|
||||||
import { ReturnService } from "@package/config/ReturnService"
|
import { ReturnService } from "@package/config/ReturnService"
|
||||||
|
|
@ -36,7 +37,7 @@ async function GetTypeDni(ae_object: GetTypeDNIOutputInterface) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await FetchData(BASE_URL_CUSTOMER_TYPEDNI, 'all', options)
|
const data: CustomerDniTypeResponseInterface = await FetchData(BASE_URL_CUSTOMER_TYPEDNI, 'all', options)
|
||||||
|
|
||||||
if (data.correct) {
|
if (data.correct) {
|
||||||
return data.object
|
return data.object
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ async function GetSale(ae_object: GetSaleOutputInterface) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
let data: GetSaleResponseInterface = await FetchData(BASE_URL_SALE, 'addProducts', options)
|
let data: GetSaleResponseInterface = await FetchData(BASE_URL_SALE, 'getSale', options)
|
||||||
|
|
||||||
if (data.correct) {
|
if (data.correct) {
|
||||||
return data.object
|
return data.object
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ async function StartSale(ae_object: StartSaleOutputInterface) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
let data : StartSaleResponseInterface = await FetchData(BASE_URL_SALE, 'addProducts', options)
|
let data : StartSaleResponseInterface = await FetchData(BASE_URL_SALE, 'startSale', options)
|
||||||
|
|
||||||
if (data.correct) {
|
if (data.correct) {
|
||||||
return data.object
|
return data.object
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
import { useSetAtom } from 'jotai';
|
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import CustomerAtom from '@atoms/states/CustomerAtom';
|
|
||||||
import { CustomerInterface, CustomerResponseInterface } from '@interfaces/ResponseInterfaces/Customer/CustomerResponseInterface';
|
import { CustomerInterface, CustomerResponseInterface } from '@interfaces/ResponseInterfaces/Customer/CustomerResponseInterface';
|
||||||
import { CustomerDniTypeInterface, CustomerDniTypeResponseInterface } from '@interfaces/ResponseInterfaces/Customer/DNITypeResponseInterface';
|
import { CustomerDniTypeInterface, CustomerDniTypeResponseInterface } from '@interfaces/ResponseInterfaces/Customer/DNITypeResponseInterface';
|
||||||
import { GetCustomer } from '@package/config/api/Customer/Customer/GetCustomer/page';
|
import { GetCustomer } from '@package/config/api/Customer/Customer/GetCustomer/page';
|
||||||
|
|
@ -17,7 +15,8 @@ export const useClientSearchLogic = () => {
|
||||||
idNumber: ''
|
idNumber: ''
|
||||||
});
|
});
|
||||||
|
|
||||||
const setCustomer = useSetAtom(CustomerAtom);
|
// Cliente seleccionado
|
||||||
|
const [customer, setCustomer] = useState<CustomerInterface | null>(null);
|
||||||
|
|
||||||
const fetchIdTypes = async () => {
|
const fetchIdTypes = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
@ -91,6 +90,7 @@ export const useClientSearchLogic = () => {
|
||||||
formData,
|
formData,
|
||||||
handleInputChange,
|
handleInputChange,
|
||||||
handleSubmitForm,
|
handleSubmitForm,
|
||||||
isAffiliated
|
isAffiliated,
|
||||||
|
customer
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
export interface GetSaleOutputInterface {
|
export interface GetSaleOutputInterface {
|
||||||
idTransaction?: string;
|
idTransaction?: string;
|
||||||
storeNumber?: number;
|
storeNumber?: string;
|
||||||
idSubsidiary?: string;
|
idSubsidiary?: number;
|
||||||
idUser?: string;
|
idUser?: string;
|
||||||
pos?: string;
|
pos?: string;
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
|
|
||||||
export interface Tax {
|
export interface TaxInteface {
|
||||||
id: string;
|
id: string;
|
||||||
idSaleDetail: string;
|
idSaleDetail: string;
|
||||||
idProduct: string;
|
idProduct: string;
|
||||||
|
|
@ -13,7 +13,7 @@ export interface Tax {
|
||||||
taxIncluded: boolean;
|
taxIncluded: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SaleDetail {
|
export interface SaleDetailInterface {
|
||||||
idDetail: string;
|
idDetail: string;
|
||||||
idTransaction: string;
|
idTransaction: string;
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -35,19 +35,19 @@ export interface SaleDetail {
|
||||||
idUser: string;
|
idUser: string;
|
||||||
creation: string;
|
creation: string;
|
||||||
lastUpdate: string;
|
lastUpdate: string;
|
||||||
taxes: Tax[];
|
taxes: TaxInteface[];
|
||||||
active: boolean;
|
active: boolean;
|
||||||
weighable: boolean;
|
weighable: boolean;
|
||||||
extendedPrice: number;
|
extendedPrice: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CustomerPromotion {
|
export interface CustomerPromotionInterface {
|
||||||
groupId: string;
|
groupId: string;
|
||||||
customerId: string;
|
customerId: string;
|
||||||
percentageDiscount: number | null;
|
percentageDiscount: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ItemPromotion {
|
export interface ItemPromotionInterface {
|
||||||
index: number;
|
index: number;
|
||||||
code: string;
|
code: string;
|
||||||
quantity: number;
|
quantity: number;
|
||||||
|
|
@ -58,14 +58,14 @@ export interface ItemPromotion {
|
||||||
weighable: boolean;
|
weighable: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Promotions {
|
export interface PromotionsInterface {
|
||||||
customer: CustomerPromotion;
|
customer: CustomerPromotionInterface;
|
||||||
items: ItemPromotion[];
|
items: ItemPromotionInterface[];
|
||||||
total: number;
|
total: number;
|
||||||
listPromotion: any[]; // Assuming this is an empty array, can adjust if more data is provided
|
listPromotion: any[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SaleTransactionObject {
|
export interface SaleTransactionObjectInterface {
|
||||||
id: string;
|
id: string;
|
||||||
idCompany: string;
|
idCompany: string;
|
||||||
idSubsidiary: number;
|
idSubsidiary: number;
|
||||||
|
|
@ -95,13 +95,13 @@ export interface SaleTransactionObject {
|
||||||
localDateTime: string | null;
|
localDateTime: string | null;
|
||||||
creation: string;
|
creation: string;
|
||||||
lastUpdate: string;
|
lastUpdate: string;
|
||||||
list: SaleDetail[];
|
list: SaleDetailInterface[];
|
||||||
promotions: Promotions;
|
promotions: PromotionsInterface;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GetSaleResponseInterface {
|
export interface GetSaleResponseInterface {
|
||||||
correct: boolean;
|
correct: boolean;
|
||||||
message: string | null;
|
message: string | null;
|
||||||
errorCode: number;
|
errorCode: number;
|
||||||
object: SaleTransactionObject;
|
object: SaleTransactionObjectInterface;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,5 +7,5 @@ export interface StartSaleResponseInterface {
|
||||||
correct: boolean;
|
correct: boolean;
|
||||||
message: string;
|
message: string;
|
||||||
errorCode: number;
|
errorCode: number;
|
||||||
object: TransactionObject;
|
object: TransactionObject | null;
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue