Cuando iniciamos un pequeño negocio en donde ya tenemos de mas vendedores es importante tener información al momento de las ventas realizadas, de pendiente de cobrar y de por lo menos un inventario básico para saber que necesitamos comprar además de saber cuales son los productos que mas se venden por mes año o día.
También es necesario tener un control de acceso para que los empleados no puedan entrar a visualizar información sensible o esta no pueda ser modificada por ellos si no tienen autorización.
Es necesario también poder ver reportes de las ventas e inventarios en tiempo real desde cualquier ubicación desde la comodidad del dispositivo móvil, sin importar el sistema operativo del dispositivo.
Todo eso se pensó al ir desarrollando el sistema de punto de venta en JCPOS2021
Este sistema puede funcionar desde cualquier servidor que soporte las tecnologías de PHP8, Apache y MariaDB que es el estándar en el mundo de internet.
Les dejo un demo de este sistema y también el código fuente antes de empezar con el manual
Bien al entrar al demo lo primero que vemos es la pantalla de acceso en la cual para el demo el usuario es admin y la contraseña es admin
Aquí tenemos la pantalla de acceso, para el demo entramos con el usuario demo y contraseña demo
Al entrar lo primero que vemos es la pantalla del dashboard, el menú lateral y menú superior,
En esta imagen vemos el menú lateral expandido pero normalmente esta colapsado, este es el dashboard que ve el usuario con todos los derechos en el sistema
ELEMENTOS DEL DASHBOARD
Si el usuario tiene derechos a los elementos del dashboard los mostrara y son los siguientes
Estas son las cajas superiores y en el primer cuadro azul nos muestra todas las ventas registradas, si le damos click en Mas info nos lleva a ver la lista de todas las ventas
En la caja verde nos muestra el total cobrado, si le damos click en mas info nos muestra la lista de lo cobrado
En la caja naranja nos muestra lo pendiente por cobrar, si le damos click en mas info no lleva a la lista de las ventas pendientes por cobrar
En la caja roja solo vemos la lista de los productos
En el grafico de ventas va mostrando el total de ventas por periodo “MES”
Vemos una grafica de los productos mas vendidosEn este grafico nos muestra los clientes que mas nos deben, si damos click en el nombre nos lleva a la lista de ventas pendientes de pago de ese cliente
MENU CONFIGURACIONES
En este menú nos muestra todos los submenús que nos servirá para dictar las reglas de funcionamiento del sistema y cuenta con los siguientes submenús 1. Datos Empresa 2. Usuarios 3. Perfiles 4. Configurar Correo 5. Bitacora
Es que es donde definiremos las variables globales del sistema
SUB MENU DATOS EMPRESA
Aquí alimentaremos el sistema por única vez con los datos de la empresa como nombre dirección ETC, estos datos son los que saldrán en los encabezados de los reportes.
En el ultimo dato donde dice 30 es para poner los días de entrega
SUB MENU USUARIOS
En este catalogo damos las altas, bajas y cambios a los usuarios
Aquí vemos la lista de todos los usuarios creados, en la columna final están los botones para modificar y eliminar, en el botón de arriba es para crear otro usuario
Al darle clic al botón agregar nos saldrá esta ventana con la cual podremos dar de alta usuarios.
En el combo que esta seleccionado vendedor es para seleccionar el perfil y el perfil define los derechos de acceso al sistemaEn esta ventana es para editar el usuario, también lo podemos cambiar de perfil
SUB MENÚ PERFILES
En este catalogo creamos los diferentes perfiles en los cuales le asignamos los derechos de acceso a las diferentes del sistema
Como vemos es un catalogo con el mismo estándar que el de usuarios, así se va a manejar en todo el sistema, en este tipo de catalogo se pueden dar las altas bajas y cambios.
Vemos como podemos activar funciones por perfil
SUB MENU CONFIGURAR CORREO
Sirve para configurar el envió de correo, estos datos nos permitirá enviar directamente los correos de las cotizaciones al correo del cliente
MENU CATALOGO DE CLIENTES
En este catalogo nos servirá para dar de alta todos los clientes, como recomendación deje el primer cliente como publico en general
Como se ve en la ilustración manejamos las altas, bajas y cambios
Al darle agregar nos pide los siguientes datos
Y al darle modificar a un cliente ya modificado nos trae los datos registrados para cambiarlos
SUB MENU BITACORA
Esta opción nos sirve como información para ver los movimientos de los usuarios en caso de error del sistema o error humano
MENU CATALOGO DE CATEGORIAS
Y antes de capturar los productos tenemos que capturar las categorías de productos, solo consta de descripción
MENU CATALOGO PRODUCTOS
Aquí es donde capturamos los productos que se van a vender, también se le asigna la existencia ese valor va disminuyendo conforme se van haciendo ventas
Así es como se ve el catalogo de productos
Al agregar un producto nos pide los siguientes datos
Esta es la ventana para modificar un producto existente, también podemos modificar la existencia
MENU COTIZACIONES
Este menú consta de dos sub menús 1. Nueva Cotización 2. Ver Cotizaciones
SUB MENU NUEVA COTIZACIÓN
Nos permite realizar cotizaciones para enviársela al cliente y si el cliente confirma convertimos la cotización en venta
Para realizar una cotización de lado izquierdo vemos los datos principales como cliente fecha, fecha de vencimiento de la cotización y a lo ultimo son los días de entrega una vez confirmada la venta.
Del lado izquierdo esta los productos que podemos vender, si le damos vender se van agregando a la lista
Si le damos guardar se genera el PDF de la cotización para poder imprimirla o enviársela por correo al cliente
SUB MENU VER COTIZACIONES
Es para ver las cotizaciones realizadas y ver si las eliminamos, copiamos o realizamos la venta
Vemos la lista de cotizaciones, en el menú opciones podemos imprimir, enviar por correo, editar y eliminar
Si le damos click se nos saldrá la ventana para enviar el PDF al correo del cliente
Ventana de envió de correo
Si le damos clic en generar venta se nos creara una venta nueva ligada con la cotización
Al darle click en generar venta en la cotización nos mandara a esta ventana
MENU VENTAS
En el menú ventas contamos con los siguientes sub menús 1. Administrar Ventas 2. Crear Venta 3. Reporte de ventas por producto 4. Reporte de ventas
En el sub menú administrar ventas vemos toda la lista de ventas, en los controles podemos imprimir en PDF la venta, editarla, eliminarla, generar un pago y ver los pagos que se han hecho a la venta del renglón
Al darle clic a la lupa blanca con fondo rosa vemos los pagos que se han realizado y en los controles podemos imprimir el comprobante de pago o eliminar el pago
Ejemplo del comprobante de pago
Cuando le damos click al icono blanco de visa con fondo verde nos sale una ventana para capturar el pago de la venta
SUB MENU REPORTE DE VENTAS POR PRODUCTO
Este reporte nos da una lista de las ventas detallado por producto
Nos da la opción para filtrarlo por producto
SUB MENU REPORTE DE VENTAS
Además de los reportes de grafico de ventas y productos mas vendidos aquí contamos con una grafica de barras con las ventas por vendedor, una grafica de ventas por comprador “Cliente”, aquí podemos filtrar por fecha además viene una opción para exportar en Excel la información.
En la parte superior vemos el rango de fecha y el botón verde para exportarlo a excel
Al darle click en el botón rango nos sale las siguientes opciones para filtrar
Nos muestra la grafica de ventas por vendedorGrafica de ventas por compradoresAl exportar en excel lo exporta en este formato
Saludos no olviden compartir esta pagina si les es útil y también comentar en la caja de comentarios cualquier duda que tengan
Instalación de ejecutable
Tambien lo podemos instalar en nuestra PC sin necesidad de tener internet para que funcione
Primeramente ejecutamos el instalador
En la primer venta nos mostrara la opción para crear el acceso directo, lo dejamos tal y como esta la imagen y le damos click en siguiente
En la siguiente ventana nos muestra como quedaria la instalación
Se comenzara a instalar
Al finalizar la instalación nos mostrara la siguiente ventana, le damos finalizar
Y listo tenemos la aplicacion instalada, posiblemente al abrilo la primera vez no funcione y salga la ventana en blanco, solo hay que cerrarlo y volverlo abrir
La Carta Porte 3.0 es la versión más reciente del complemento que debe acompañar a los Comprobantes Fiscales Digitales por Internet (CFDI) que amparan el traslado de mercancías en México. Esta versión entró en vigor el 25 de noviembre de 2023 y es obligatoria a partir del 1 de enero de 2024.
La Carta Porte 3.0 tiene como objetivo mejorar la trazabilidad de las mercancías que se transportan en México, así como facilitar el cumplimiento de las obligaciones fiscales de los contribuyentes. Para ello, incluye nuevos campos y requisitos que deben ser proporcionados por los emisores de los CFDI.
Entre los principales cambios que introduce la Carta Porte 3.0 se encuentran los siguientes:
La inclusión de nuevos datos sobre el transporte de las mercancías, como el medio de transporte, la ruta y la fecha de salida y llegada.
La obligación de identificar al destinatario de las mercancías, incluso si es una persona física.
La posibilidad de utilizar un identificador único para el CFDI, lo que facilitará su consulta y verificación.
La Carta Porte 3.0 es un complemento obligatorio para todos los contribuyentes que realicen el traslado de mercancías en México, independientemente de su tamaño o actividad económica. Los contribuyentes que no cumplan con esta obligación podrán ser sancionados por el Servicio de Administración Tributaria (SAT).
A continuación, se presentan algunos ejemplos de los nuevos campos que deben ser proporcionados en la Carta Porte 3.0:
Medio de transporte: Se debe indicar el tipo de medio de transporte utilizado para el traslado de las mercancías, como camión, tren, barco o avión.
Ruta: Se debe indicar la ruta que se seguirá para el traslado de las mercancías, incluyendo los puntos de origen y destino.
Fecha de salida y llegada: Se debe indicar la fecha en que se iniciará y concluirá el traslado de las mercancías.
Destinatario: Se debe identificar al destinatario de las mercancías, incluyendo su nombre, RFC y domicilio.
Identificador único del CFDI: Se debe proporcionar un identificador único para el CFDI, el cual podrá ser generado por el SAT o por el emisor del CFDI.
Para obtener más información sobre la Carta Porte 3.0, se puede consultar el sitio web del SAT.
La descarga masiva es un proceso que permite descargar un gran número de archivos o datos de forma simultánea. En el contexto de las facturas electrónicas en México, la descarga masiva se refiere al proceso de descargar un gran número de CFDI de forma simultánea desde el portal del SAT.
Por lo tanto vamos agregar este utilidad utilizando las librerías de https://www.phpcfdi.com/librerias/
Bien como ya saben para hacer una venta se requiere saber de que sucursal es así como ciertas configuraciones particulares por sucursal como la configuración de las series electrónicas para el timbrado del CFDI, así como los datos que saldran en las impresiones de los reportes, como la dirección etc
Por esta ocasión agregaremos lo siguientes datos, posterior mente se pueden agregar mas si se necesitan
Primeramente creamos el archivo de migración app/database/migrations/2023-02-14110147_Branchoffices.php , se dan cuenta que dice Branchoffice, es por que en su momento lo quise empezar en ingles para practicar
Creamos el archivo del modelo usuarios por sucursal app/model/UsuariosSucursalModel.php
<?php
namespace App\Models;
use CodeIgniter\Model;
class UsuariosSucursalModel extends Model
{
protected $table = 'usuarios_sucursal';
protected $primaryKey = 'id';
protected $useAutoIncrement = true;
protected $returnType = 'array';
protected $useSoftDeletes = true;
protected $allowedFields = ['id', 'idEmpresa', 'idSucursal', 'idUsuario', 'status', 'created_at', 'updated_at', 'deleted_at'];
protected $useTimestamps = true;
protected $createdField = 'created_at';
protected $deletedField = 'deleted_at';
protected $validationRules = [];
protected $validationMessages = [];
protected $skipValidation = false;
public function mdlSucursalesPorUsuario($sucursal, $empresasID)
{
$result = $this->db->table('users a, usuariosempresa b')
->select(
'ifnull(a.id,0) as id
,a.username
,b.idEmpresa
,' . $sucursal . ' as idSucursal
,ifnull((select status
from usuarios_sucursal z
where z.idUsuario = a.id
and z.idEmpresa=b.idEmpresa
and z.idSucursal=' . $sucursal . '
),\'off\') as status
,ifnull((select id
from usuarios_sucursal z
where z.idUsuario = a.id
and z.idEmpresa=b.idEmpresa
and z.idSucursal=' . $sucursal . '
),0) as idSucursalUsuario
'
)
->where('a.id', 'b.idUsuario', FALSE)
->where('b.idEmpresa', $empresasID);
return $result;
}
}
Creamos el archivo app/controller/BranchofficesController.php
<?php
namespace App\Controllers;
use App\Controllers\BaseController;
use \App\Models\{
BranchofficesModel
};
use App\Models\LogModel;
use CodeIgniter\API\ResponseTrait;
use App\Models\EmpresasModel;
use App\Models\UsuariosSucursalModel;
class BranchofficesController extends BaseController {
use ResponseTrait;
protected $log;
protected $branchoffices;
protected $empresas;
protected $usuariosPorSucursal;
public function __construct() {
$this->branchoffices = new BranchofficesModel();
$this->log = new LogModel();
$this->empresas = new EmpresasModel();
$this->usuariosPorSucursal = new UsuariosSucursalModel();
helper('menu');
}
public function index() {
helper('auth');
$idUser = user()->id;
$titulos["empresas"] = $this->empresas->mdlEmpresasPorUsuario($idUser);
if (count($titulos["empresas"]) == "0") {
$empresasID[0] = "0";
} else {
$empresasID = array_column($titulos["empresas"], "id");
}
if ($this->request->isAJAX()) {
$datos = $this->branchoffices->select('id
,key
,name
,cologne
,city
,postalCode
,timeDifference
,tax,dateAp
,phone
,fax
,companie
,created_at
,deleted_at
,updated_at')->where('deleted_at', null)
->whereIn('companie', $empresasID);;
return \Hermawan\DataTables\DataTable::of($datos)->toJson(true);
}
// $empresas = $this->empresas->select("id,nombre")->asObject()->findAll();
// $titulos["empresas"] = $empresas;
$titulos["title"] = lang('branchoffices.title');
$titulos["subtitle"] = lang('branchoffices.subtitle');
return view('branchoffices', $titulos);
}
/**
* Read Branchoffices
*/
public function getBranchoffices() {
$idBranchoffices = $this->request->getPost("idBranchoffices");
$datosBranchoffices = $this->branchoffices->find($idBranchoffices);
echo json_encode($datosBranchoffices);
}
/**
* Save or update Branchoffices
*/
public function save() {
helper('auth');
$userName = user()->username;
$idUser = user()->id;
$datos = $this->request->getPost();
if ($datos["idBranchoffices"] == 0) {
try {
if ($this->branchoffices->save($datos) === false) {
$errores = $this->branchoffices->errors();
foreach ($errores as $field => $error) {
echo $error . " ";
}
return;
}
$dateLog["description"] = lang("vehicles.logDescription") . json_encode($datos);
$dateLog["user"] = $userName;
$this->log->save($dateLog);
echo "Guardado Correctamente";
} catch (\PHPUnit\Framework\Exception $ex) {
echo "Error al guardar " . $ex->getMessage();
}
} else {
if ($this->branchoffices->update($datos["idBranchoffices"], $datos) == false) {
$errores = $this->branchoffices->errors();
foreach ($errores as $field => $error) {
echo $error . " ";
}
return;
} else {
$dateLog["description"] = lang("branchoffices.logUpdated") . json_encode($datos);
$dateLog["user"] = $userName;
$this->log->save($dateLog);
echo "Actualizado Correctamente";
return;
}
}
return;
}
/**
* Delete Branchoffices
* @param type $id
* @return type
*/
public function delete($id) {
$infoBranchoffices = $this->branchoffices->find($id);
helper('auth');
$userName = user()->username;
if (!$found = $this->branchoffices->delete($id)) {
return $this->failNotFound(lang('branchoffices.msg.msg_get_fail'));
}
$logData["description"] = lang("branchoffices.logDeleted") . json_encode($infoBranchoffices);
$logData["user"] = $userName;
$this->log->save($logData);
return $this->respondDeleted($found, lang('branchoffices.msg_delete'));
}
public function usuariosPorSucursal($sucursal) {
helper('auth');
$idUser = user()->id;
$datosSucursal = $this->branchoffices->select("companie as empresa")->where("id",$sucursal)->first();
if(isset($datosSucursal["empresa"])){
$idEmpresa = $datosSucursal["empresa"];
}else{
$idEmpresa = -1;
}
$usuarios = $this->usuariosPorSucursal->mdlSucursalesPorUsuario($sucursal, $idEmpresa);
return \Hermawan\DataTables\DataTable::of($usuarios)->toJson(true);
}
/**
* Activar Desactivar Usuario Por Empresa
*/
public function ActivarDesactivar() {
$datos = $this->request->getPost();
if ($datos["id"] > 0) {
//ACTUALIZA SI EXISTE
if ($this->usuariosPorSucursal->update($datos["id"], $datos) === false) {
$errores = $this->usuariosPorSucursal->errors();
foreach ($errores as $field => $error) {
echo $error . " ";
}
return;
}
echo "ok";
} else {
//INSERTA SI NO EXISTE
if ($this->usuariosPorSucursal->save($datos) === false) {
$errores = $this->usuariosPorSucursal->errors();
foreach ($errores as $key => $error) {
echo $error . " ";
}
return;
}
echo "ok";
}
}
/**
* Get Storages via AJax
*/
public function getSucursalesAjax() {
$request = service('request');
$postData = $request->getPost();
$response = array();
// Read new token and assign in $response['token']
$response['token'] = csrf_hash();
helper('auth');
$userName = user()->username;
$idUser = user()->id;
$sucursalesPorUsuario = $this->usuariosPorSucursal->select("*")
->where("idUsuario", $idUser)
->where("status", "on")->findAll();
$sucursalesPorUsuario = array_column($sucursalesPorUsuario, "idSucursal");
if (!isset($postData['searchTerm'])) {
// Fetch record
$sucursales = new BranchofficesModel();
$listSucursales = $sucursales->select('id,key,name')->where("deleted_at", null)
->whereIn("id", $sucursalesPorUsuario)
->where("companie", $postData["idEmpresa"])
->orderBy('id')
->orderBy('key')
->orderBy('name')
->findAll();
} else {
$searchTerm = $postData['searchTerm'];
// Fetch record
$sucursales = new BranchofficesModel();
$listSucursales = $sucursales->select('id,key,name')
->where("deleted_at", null)
->whereIn("id", $sucursalesPorUsuario)
->where("companie", $postData["idEmpresa"])
->like('name', $searchTerm)
->orLike('id', $searchTerm)
->orLike('key', $searchTerm)
->findAll();
}
$data = array();
$data[] = array(
"id" => 0,
"text" => "0 Todas las sucursales",
);
foreach ($listSucursales as $sucursal) {
$data[] = array(
"id" => $sucursal['id'],
"text" => $sucursal['key'] . ' ' . $sucursal['name'],
);
}
$response['data'] = $data;
return $this->response->setJSON($response);
}
}
Creamos el archivo del controlador para usuarios por sucursal app/controller/UsuariosSucursalController.php
<?php
namespace App\Controllers;
use App\Controllers\BaseController;
use \App\Models\{
UsuariosSucursalModel
};
use App\Models\LogModel;
use CodeIgniter\API\ResponseTrait;
class UsuariosSucursalController extends BaseController {
use ResponseTrait;
protected $log;
protected $usuariosSucursal;
public function __construct() {
$this->usuariosSucursal = new UsuariosSucursalModel();
$this->log = new LogModel();
helper('menu');
}
public function index() {
if ($this->request->isAJAX()) {
$datos = $this->usuariosSucursal>select('id,idEmpresa,idSucursal,idUsuario,status,created_at,updated_at,deleted_at')->where('deleted_at', null);
return \Hermawan\DataTables\DataTable::of($datos)->toJson(true);
}
$titulos["title"] = "Usuarios Sucursal";
$titulos["subtitle"] = "Usuarios Por Sucursal";
return view('usuariosAlmacen', $titulos);
}
/**
* Read Usuariosempresa
*/
public function getUsuariosAlmacen() {
$idUsuariosAlmacen = $this->request->getPost("idUsuariosSucursal");
$datosUsuariosAlmacen = $this->usuariosAlmacen->find($idUsuariosAlmacen);
echo json_encode($datosUsuariosAlmacen);
}
/**
* Save or update Usuariosempresa
*/
public function save() {
helper('auth');
$userName = user()->username;
$idUser = user()->id;
$datos = $this->request->getPost();
if ($datos["idUsuariosSucursal"] == 0) {
try {
if ($this->usuariosSucursal->save($datos) === false) {
$errores = $this->usuariosSucursal->errors();
foreach ($errores as $field => $error) {
echo $error . " ";
}
return;
}
$dateLog["description"] = "Usuarios Por Sucursal" . json_encode($datos);
$dateLog["user"] = $userName;
$this->log->save($dateLog);
echo "Guardado Correctamente";
} catch (\PHPUnit\Framework\Exception $ex) {
echo "Error al guardar " . $ex->getMessage();
}
} else {
if ($this->usuariosSucursal->update($datos["idUsuariossucursal"], $datos) == false) {
$errores = $this->usuariosSucursal->errors();
foreach ($errores as $field => $error) {
echo $error . " ";
}
return;
} else {
$dateLog["description"] = lang("usuariosSucursal.logUpdated") . json_encode($datos);
$dateLog["user"] = $userName;
$this->log->save($dateLog);
echo "Actualizado Correctamente";
return;
}
}
return;
}
/**
* Delete Usuariosempresa
* @param type $id
* @return type
*/
public function delete($id) {
$infoUsuariosSucursal = $this->usuariosSucursal->find($id);
helper('auth');
$userName = user()->username;
if (!$found = $this->usuariosSucursal->delete($id)) {
return $this->failNotFound(lang('usuariosempresa.msg.msg_get_fail'));
}
$this->usuariosSucursal->purgeDeleted();
$logData["description"] = "Datos Anteriores Usuarios Por Sucursal" . json_encode($infoUsuariosSucursal);
$logData["user"] = $userName;
$this->log->save($logData);
return $this->respondDeleted($found, lang('usuariossucursal.msg_delete'));
}
}
Creamos el archivo principal de la vista de sucursales app/views/branchoffice.php este archivo hará una inclusión a los modales de usuarios por sucursal y captura de sucursales
Bien primero que nada necesitamos crear la tabla en la base de datos, normalmente creamos la tabla directamente en la base de datos, pero como estamos trabajando en CodeIgniter 4 creamos el archivo de migración en app/Database/Migrations/2023-04-24060002_Products.php
Agregamos el archivo del controlador para las operaciones de validacion altas bajas y cambios en app/controllers/ProductsController.php
<?php
namespace App\Controllers;
use App\Controllers\BaseController;
use App\Models\ProductsModel;
use App\Models\LogModel;
use CodeIgniter\API\ResponseTrait;
use App\Models\CategoriasModel;
use App\Models\EmpresasModel;
use App\Models\QuotesDetailsModel;
use App\Models\SellsDetailsModel;
use App\Models\Tipos_movimientos_inventarioModel;
class ProductsController extends BaseController {
use ResponseTrait;
protected $log;
protected $products;
protected $empresa;
protected $categorias;
protected $sellsDetails;
protected $quoteDetails;
protected $tiposMovimientoInventario;
public function __construct() {
$this->products = new ProductsModel();
$this->log = new LogModel();
$this->categorias = new CategoriasModel();
$this->empresa = new EmpresasModel();
$this->sellsDetails = new SellsDetailsModel();
$this->quoteDetails = new QuotesDetailsModel();
$this->tiposMovimientoInventario = new Tipos_movimientos_inventarioModel();
helper('menu');
}
public function index() {
helper('auth');
$idUser = user()->id;
$titulos["empresas"] = $this->empresa->mdlEmpresasPorUsuario($idUser);
if (count($titulos["empresas"]) == "0") {
$empresasID[0] = "0";
} else {
$empresasID = array_column($titulos["empresas"], "id");
}
if ($this->request->isAJAX()) {
$datos = $this->products->mdlProductos($empresasID);
return \Hermawan\DataTables\DataTable::of($datos)->toJson(true);
}
$titulos["categorias"] = $this->categorias->select("*")->where("deleted_at", null)->asArray()->findAll();
$titulos["title"] = lang('products.title');
$titulos["subtitle"] = lang('products.subtitle');
return view('products', $titulos);
}
public function getAllProducts($empresa) {
helper('auth');
$idUser = user()->id;
$titulos["empresas"] = $this->empresa->mdlEmpresasPorUsuario($idUser);
if (count($titulos["empresas"]) == "0") {
$empresasID[0] = "0";
} else {
$empresasID = array_column($titulos["empresas"], "id");
}
if ($this->request->isAJAX()) {
$datos = $this->products->mdlProductosEmpresa($empresasID, $empresa);
return \Hermawan\DataTables\DataTable::of($datos)->toJson(true);
}
}
public function getAllProductsInventory($empresa, $idStorage, $idTipoMovimiento) {
helper('auth');
$idUser = user()->id;
$titulos["empresas"] = $this->empresa->mdlEmpresasPorUsuario($idUser);
if (count($titulos["empresas"]) == "0") {
$empresasID[0] = "0";
} else {
$empresasID = array_column($titulos["empresas"], "id");
}
//BUSCAMOS EL TIPO DE MOVIMIENTO SI ES ENTRADA O SALIDA
$tiposMovimiento = $this->tiposMovimientoInventario->select("*")
->wherein("idEmpresa", $empresasID)
->where("id", $idTipoMovimiento)->first();
if ($tiposMovimiento == null) {
$datos = $this->products->mdlProductosEmpresaInventarioEntrada($empresasID, $empresa);
return \Hermawan\DataTables\DataTable::of($datos)->toJson(true);
}
if ($tiposMovimiento["tipo"] == "ENT") {
if ($this->request->isAJAX()) {
$datos = $this->products->mdlProductosEmpresaInventarioEntrada($empresasID, $empresa);
return \Hermawan\DataTables\DataTable::of($datos)->toJson(true);
}
}
if ($tiposMovimiento["tipo"] == "SAL") {
if ($this->request->isAJAX()) {
$datos = $this->products->mdlProductosEmpresaInventarioSalida($empresasID, $empresa);
return \Hermawan\DataTables\DataTable::of($datos)->toJson(true);
}
}
$datos = $this->products->mdlProductosEmpresaInventarioEntrada($empresasID, $empresa);
return \Hermawan\DataTables\DataTable::of($datos)->toJson(true);
}
/**
* Get Unidad SAT via AJax
*/
public function getUnidadSATAjax() {
$request = service('request');
$postData = $request->getPost();
$response = array();
// Read new token and assign in $response['token']
$response['token'] = csrf_hash();
if (!isset($postData['searchTerm'])) {
// Fetch record
$listUnidadesSAT = $this->catalogosSAT->clavesUnidades40()->searchByField("texto", "%$%", 100);
} else {
$searchTerm = $postData['searchTerm'];
// Fetch record
$listUnidadesSAT = $this->catalogosSAT->clavesUnidades40()->searchByField("texto", "%$searchTerm%", 100);
}
$data = array();
foreach ($listUnidadesSAT as $unidadSAT => $value) {
$data[] = array(
"id" => $value->id(),
"text" => $value->id() . ' ' . $value->texto(),
);
}
$response['data'] = $data;
return $this->response->setJSON($response);
}
/**
* Get Unidad SAT via AJax
*/
public function getProductosSATAjax() {
$request = service('request');
$postData = $request->getPost();
$response = array();
// Read new token and assign in $response['token']
$response['token'] = csrf_hash();
if (!isset($postData['searchTerm'])) {
// Fetch record
$listProducts = $this->catalogosSAT->productosServicios40()->searchByField("texto", "%$searchTerm%", 50);
} else {
$searchTerm = $postData['searchTerm'];
// Fetch record
$listProducts = $this->catalogosSAT->productosServicios40()->searchByField("texto", "%$searchTerm %", 50);
}
$data = array();
foreach ($listProducts as $productosSAT => $value) {
$data[] = array(
"id" => $value->id(),
"text" => $value->id() . ' ' . $value->texto(),
);
}
$response['data'] = $data;
return $this->response->setJSON($response);
}
/**
* Get Products via AJax
*/
public function getProductsAjaxSelect2() {
$request = service('request');
$postData = $request->getPost();
$response = array();
// Read new token and assign in $response['token']
$response['token'] = csrf_hash();
$products = new ProductsModel();
$idEmpresa = $postData['idEmpresa'];
if (!isset($postData['searchTerm'])) {
// Fetch record
$listProducts = $products->select('id,code,description')->where("deleted_at", null)
->where('idEmpresa', $idEmpresa)
->orderBy('id')
->orderBy('code')
->orderBy('description')
->findAll(1000);
} else {
$searchTerm = $postData['searchTerm'];
// Fetch record
$listProducts = $products->select('id,code,description')->where("deleted_at", null)
->where('idEmpresa', $idEmpresa)
->groupStart()
->like('description', $searchTerm)
->orLike('id', $searchTerm)
->orLike('code', $searchTerm)
->groupEnd()
->findAll(1000);
}
$data = array();
$data[] = array(
"id" => 0,
"text" => "0 Todos Los Productos",
);
foreach ($listProducts as $product) {
$data[] = array(
"id" => $product['id'],
"text" => $product['id'] . ' ' . $product['id'] . ' ' . $product['code'] . ' ' . $product['description'],
);
}
$response['data'] = $data;
return $this->response->setJSON($response);
}
/**
* Read Products
*/
public function getProducts() {
helper('auth');
$idUser = user()->id;
$titulos["empresas"] = $this->empresa->mdlEmpresasPorUsuario($idUser);
if (count($titulos["empresas"]) == "0") {
$empresasID[0] = "0";
} else {
$empresasID = array_column($titulos["empresas"], "id");
}
$idProducts = $this->request->getPost("idProducts");
$datosProducts = $this->products->mdlGetProductoEmpresa($empresasID, $idProducts);
echo json_encode($datosProducts);
}
/**
* Save or update Products
*/
public function save() {
helper('auth');
$userName = user()->username;
$idUser = user()->id;
$datos = $this->request->getPost();
var_dump($datos);
$imagenProducto = $this->request->getFile("imagenProducto");
$datos["routeImage"] = "";
if ($imagenProducto) {
if ($imagenProducto->getClientExtension() <> "png") {
return lang("empresas.pngFileExtensionIncorrect");
}
$datos["routeImage"] = $imagenProducto->getRandomName();
}
if ($datos["idProducts"] == 0) {
try {
if ($this->products->save($datos) === false) {
$errores = $this->products->errors();
foreach ($errores as $field => $error) {
echo $error . " ";
}
return;
}
$dateLog["description"] = lang("vehicles.logDescription") . json_encode($datos);
$dateLog["user"] = $userName;
$this->log->save($dateLog);
if ($imagenProducto <> null) {
$imagenProducto->move("images/products", $datos["routeImage"]);
}
echo "Guardado Correctamente";
} catch (\PHPUnit\Framework\Exception $ex) {
echo "Error al guardar " . $ex->getMessage();
}
} else {
$dataPrevious = $this->products->find($datos["idProducts"]);
if ($this->products->update($datos["idProducts"], $datos) == false) {
$errores = $this->products->errors();
foreach ($errores as $field => $error) {
echo $error . " ";
}
return;
} else {
$dateLog["description"] = lang("products.logUpdated") . json_encode($datos);
$dateLog["user"] = $userName;
$this->log->save($dateLog);
if ($imagenProducto <> null) {
if (file_exists("images/products/" . $dataPrevious["routeImage"])) {
unlink("images/products/" . $dataPrevious["routeImage"]);
}
$imagenProducto->move("images/products", $datos["routeImage"]);
}
echo "Actualizado Correctamente";
return;
}
}
return;
}
/**
* Delete Products
* @param type $id
* @return type
*/
public function delete($id) {
if ($this->sellsDetails->select("id")->where("idProduct", $id)->countAllResults() > 0) {
$this->products->db->transRollback();
return $this->failValidationError("No se puede borrar ya que hay ventas con este producto");
}
if ($this->quoteDetails->select("id")->where("idProduct", $id)->countAllResults() > 0) {
$this->products->db->transRollback();
return $this->failValidationError("No se puede borrar ya que hay cotizaciones con este producto");
}
$infoProducts = $this->products->find($id);
helper('auth');
$userName = user()->username;
if (!$found = $this->products->delete($id)) {
$this->products->db->transRollback();
return $this->failNotFound(lang('products.msg.msg_get_fail'));
}
if ($infoProducts["routeImage"] != "") {
if (file_exists("images/products/" . $infoProducts["routeImage"])) {
unlink("images/products/" . $infoProducts["routeImage"]);
}
}
$this->products->purgeDeleted();
$logData["description"] = lang("products.logDeleted") . json_encode($infoProducts);
$logData["user"] = $userName;
$this->log->save($logData);
$this->products->db->transCommit();
return $this->respondDeleted($found, lang('products.msg_delete'));
}
/**
* Get Vehiculos via AJax
*/
public function getProductsAjax() {
$request = service('request');
$postData = $request->getPost();
$response = array();
// Read new token and assign in $response['token']
$response['token'] = csrf_hash();
$custumers = new VehiculosModel();
$idEmpresa = $postData['idEmpresa'];
if (!isset($postData['searchTerm'])) {
// Fetch record
$listProducts = $products->select('id,description')->where("deleted_at", null)
->where('idEmpresa', $idEmpresa)
->orderBy('id')
->orderBy('descripcion')
->findAll(1000);
} else {
$searchTerm = $postData['searchTerm'];
// Fetch record
$listProducts = $products->select('id,description')->where("deleted_at", null)
->where('idEmpresa', $idEmpresa)
->groupStart()
->like('descripcion', $searchTerm)
->orLike('id', $searchTerm)
->groupEnd()
->findAll(1000);
}
$data = array();
foreach ($listProducts as $product) {
$data[] = array(
"id" => $custumers['id'],
"text" => $custumers['id'] . ' ' . $product['description'],
);
}
$response['data'] = $data;
return $this->response->setJSON($response);
}
}
Ahora creamos la interfaz es decir la vista, para ello tendremos el archivo principal app/views/products.php y este incluirá los archivos secundarios, una forma que a mi parecer se organiza mejor para no tener todo en un solo archivo.
En el diseño estaran los bloques separados en pestañas “TABS”
JCPOS es un sistema de punto de venta (POS) de código abierto desarrollado en PHP. Recientemente, JCPOS se ha migrado a CodeIgniter 4, lo que ha mejorado significativamente su rendimiento y escalabilidad. En este artículo, exploraremos los detalles de la migración de JCPOS a CodeIgniter 4 y sus beneficios.
Antes de profundizar en la migración de JCPOS a CodeIgniter 4, es importante comprender qué es CodeIgniter y por qué es una opción popular para desarrolladores de PHP.
CodeIgniter es un framework PHP de código abierto que se utiliza para desarrollar aplicaciones web dinámicas y sitios web. Es popular debido a su facilidad de uso, velocidad y seguridad. Además, CodeIgniter tiene una gran comunidad de desarrolladores que proporciona soporte y actualizaciones regulares.
La migración de JCPOS a CodeIgniter 4 implica actualizar el código de JCPOS para que sea compatible con la última versión del framework. CodeIgniter 4 presenta varias mejoras y cambios significativos en comparación con su versión anterior, CodeIgniter 3. Algunas de estas mejoras incluyen:
Mayor rendimiento y velocidad gracias a la utilización de características avanzadas de PHP 7
Soporte mejorado para la creación de APIs RESTful
Mejoras en la seguridad y en la gestión de sesiones
Uso de clases y métodos modernos para mejorar la legibilidad y mantenibilidad del código
Flexibilidad y escalabilidad mejoradas gracias a su arquitectura modular
Para migrar JCPOS a CodeIgniter 4, los desarrolladores tuvieron que actualizar el código de JCPOS para utilizar las nuevas características y métodos de CodeIgniter 4. Esto incluyó cambios en la estructura del directorio, en la configuración del archivo y en el uso de nuevos métodos y características de CodeIgniter 4.
Una vez completada la migración, JCPOS obtuvo varios beneficios significativos. En primer lugar, el rendimiento y la velocidad de JCPOS se mejoraron considerablemente gracias a la utilización de características avanzadas de PHP 7 y a la optimización de la arquitectura de CodeIgniter 4. Esto significa que el sistema puede manejar más transacciones y usuarios simultáneamente, lo que es especialmente importante en entornos de punto de venta de alta demanda.
Además, la migración a CodeIgniter 4 proporcionó una mayor flexibilidad y escalabilidad a JCPOS. La arquitectura modular de CodeIgniter 4 permite que los desarrolladores de JCPOS agreguen fácilmente nuevas funcionalidades y características al sistema sin afectar negativamente al rendimiento o la estabilidad. Esto significa que JCPOS puede adaptarse y crecer para satisfacer las necesidades cambiantes de sus usuarios.
En conclusión, la migración de JCPOS a CodeIgniter 4 ha mejorado significativamente el rendimiento, la escalabilidad y la flexibilidad del sistema. Los desarrolladores de JCPOS han aprovechado las características avanzadas y la arquitectura modular de CodeIgniter 4 para optimizar el sistema y garantizar su compatibilidad con las últimas tecnologías y tendencias en el desarrollo web.
Se han hecho algunos cambios significativos al arqueo de caja y son los siguientes
Se agrego una opción en datos empresa para especificar si se va usar el arqueo se caja, en caso de que no este activado se podrá realizar una venta aunque no este abierta una caja
En configuración ->Datos empresa definimos si se validara el arqueo de caja
En arqueo de caja se agrego para poder escoger con que usuario/vendedor se abrirá la caja, antes solo se podía abrir con el usuario con el que se inicio sesión
De lado izquierdo vemos como abrimos la caja con el usuario asd y en el centro vemos como podemos escoger con un combo el usuario/vendedor
En el fuente archivo configuración.php se agrego una constante llamada debug, lo ideal si tenemos el programa en XAMPP es que esa variable este como false, pero si queremos debugear directamente con PHP sin utilizar el archivo .htaccess debemos ponerlo en true
normalmente debe de estar como false
Se agrego el campo caja en datosempresa lo puede agregar con el siguiente código SQL
ALTER TABLE `datosempresa` ADD `caja` VARCHAR(5) NULL AFTER `diasEntrega`;
En este vídeo meditaremos cual sera la mejor opción para el punto de venta, si guardar las imagenes en filesystem o en base de datos FORO https://foro.cesarsystems.com.mx/
Usamos cookies en nuestro sitio web para brindarle la experiencia más relevante recordando sus preferencias y visitas repetidas. Al hacer clic en "Aceptar", acepta el uso de TODAS las cookies.
This website uses cookies to improve your experience while you navigate through the website. Out of these cookies, the cookies that are categorized as necessary are stored on your browser as they are essential for the working of basic functionalities of the website. We also use third-party cookies that help us analyze and understand how you use this website. These cookies will be stored in your browser only with your consent. You also have the option to opt-out of these cookies. But opting out of some of these cookies may have an effect on your browsing experience.
Necessary cookies are absolutely essential for the website to function properly. This category only includes cookies that ensures basic functionalities and security features of the website. These cookies do not store any personal information.
Any cookies that may not be particularly necessary for the website to function and is used specifically to collect user personal data via analytics, ads, other embedded contents are termed as non-necessary cookies. It is mandatory to procure user consent prior to running these cookies on your website.