Documentación técnica del plugin PagosMultiples
Documento para desarrolladores que necesiten mantener el plugin, ampliarlo o integrarlo con otros. Describe los modelos, los procesos, los asientos que genera y los puntos de extensión.
🗃️ Modelos y tablas
- Base\PaymentReceiptGroup: base de las agrupaciones. CustomerReceiptGroup (
ppmm_customer_receipts) y SupplierReceiptGroup (ppmm_supplier_receipts).status:STATUS_PENDING(0) oSTATUS_CHARGED(1, aprobado).groupdate: fecha de pago de los recibos y del asiento.idbank: cuenta bancaria; el asiento usa sucodsubcuenta.idcompany,idcurrencyeidseriefiltran los recibos que se pueden añadir. La serie aporta además elcanaldel asiento.groupreceipts: una línea por cliente o proveedor (true) o por recibo (false).noentry: aprobar sin asiento.identry: asiento generado al aprobar.receiptsytotal: recuento e importe, que recalculaupdateTotal()al añadir o quitar recibos.idagent: solo en la de clientes.
- Extension/Table/recibospagoscli y recibospagosprov: añaden
idmultiplea los recibos, conON DELETE SET NULL. Es el único enlace entre recibo y agrupación. - Base\BankCheck: base de los talones. CustomerBankCheck (
ppmm_customer_bankchecks, prefijoTAL-,idcustomer) y SupplierBankCheck (ppmm_supplier_bankchecks, prefijoTALP-,idsupplier).idmultiple: agrupación del talón, conON DELETE RESTRICT: una agrupación con talones no se puede borrar.codsubaccount: subcuenta de cartera.idbank: banco de depósito o cobro. Si está vacío,test()toma el de la agrupación.identry: asiento del cobro o cargo.- Fechas
delivered,expiration,deposited,chargedyreturned. type: enum BankCheckType (check,promissory).status: enum BankCheckStatus (UNTRACKED 1, PENDING 10, DEPOSITED 30, RETURNED 50, CHARGED 90). En los emitidos, PENDING se muestra como «Emitido» y CHARGED como «Cargado» (selectIssued()).expired: atributo calculado enloadFromData(), no se guarda: PENDING o DEPOSITED conexpirationhasta hoy.- El cliente o proveedor es obligatorio solo sin
idmultiple.
- AgentSettlement (
ppmm_agent_settlement): liquidación de agente, con la mismaidque el cobro múltiple (ON DELETE CASCADE). Guarda el recuento de billetes y monedas,total_diets,bankchecks,totalydifference. Conautomatic,test()recalcula el total ydifference = total + total_diets + bankchecks - total del grupo. - Extension/Table/agentes:
codsubaccount_walletycodsubaccount_difference, que usa la contabilización del cobro múltiple, ycodsubaccount_payment, que solo existe en la tabla y en el XMLView y el código no usa. - Join\ReciboClienteAdd y Join\ReciboProveedorAdd: recibos con su factura y su cliente o proveedor, para la pestaña Añadir. Join\AccountingDetail: partidas con su asiento, para las pestañas de asiento.
🚦 Estado del talón
El estado se calcula siempre en test() mediante updateStatus(), salvo que ya sea RETURNED, a partir de los datos del talón y por este orden:
- Con
identry, CHARGED. - Con
deposited, DEPOSITED. - Con
codsubaccount, PENDING. - Con
idmultiple, CHARGED concharged = delivered: se cobra con el asiento de la agrupación. - En otro caso, UNTRACKED.
Sin identry se vacía charged. Por eso las acciones no fijan el estado, sino los datos de los que depende; la excepción es cancelar, que pone RETURNED.
isLocked() es true con identry, en DEPOSITED, en RETURNED con subcuenta de cartera o si la agrupación no está pendiente. Bloqueado, delete() falla y checkLocked() rechaza el guardado si cambia algún campo de lockedFields() (codsubaccount, delivered, expiration, issuerbank, number, total, type y el cliente o proveedor). idbank no está en esa lista porque las acciones lo escriben; solo se bloquea en pantalla.
🔄 Procesos
- Lib/PagosMultiples/EditPaymentReceiptGroup: base de
EditCustomerReceiptGroupyEditSupplierReceiptGroup. Crea las pestañas y los botones según el estado, y delega las accionesadd-receipts,remove-receipts,charged-receipts(aprobar) yreopen-receiptsen PaymentReceiptGroupActions. - PaymentReceiptGroupActions, dentro de una transacción:
- Aprobar marca cada recibo como pagado con
disablePaymentGeneration(). Crea un pago (PagoCliente/PagoProveedor) concustomid= id de la agrupación ydisableAccountingGeneration(), genera el asiento salvonoentryy poneSTATUS_CHARGED. Falla si algún recibo ya está pagado. - Reabrir borra el asiento (con
editable = trueen memoria), crea un pago negativo por recibo, los deja sin pagar dentro de la agrupación y vuelve aSTATUS_PENDING.
- Aprobar marca cada recibo como pagado con
- PaymentReceiptGroup::delete(): borra el asiento, deja los recibos sin pagar y sin
idmultiple, y borra los pagos con sucustomid. - EditCustomerReceiptGroup: añade la pestaña de liquidación, solo con agente, y la acción AJAX
cash-calculateque usaAssets/JS/EditCustomerReceiptGroup.jspara recalcular en pantalla. - Lib/PagosMultiples/EditBankCheck: base de
EditCustomerBankCheckyEditSupplierBankCheck. Las fichas hijas solo definenaddActionButtons()ysubjectColumn(). Acciones:deposit-bankcheckyundo-deposit-bankcheckescriben o vacíandepositedeidbank.charged-bankcheck,refund-bankcheckycancel-bankcheckdelegan en BankCheckToAccounting.- En UNTRACKED, cancelar solo pone RETURNED y
returned. - La pestaña de asiento muestra las partidas con
asientos.documentoigual a la referencia del talón o del asientoidentry.
- Lib/PagosMultiples/BankCheckBatch: depósito y cobro o cargo en lote desde
ListFacturaClienteyListFacturaProveedor. Descarta los talones en un estado no válido y avisa conbankchecks-skipped.- El core no envía los
codes[]marcados a los modales, así queAssets/JS/BankCheckBatch.js(PMultiples.BankCheckBatch) los copia al formulario del modal. - Se carga con AssetLoader, que añade
?v=filemtime.
- El core no envía los
- BankCheck::notifyExpiring(): aviso con
Tools::log()al cargar la vista principal de los listados de facturas. UsawhereExpiring(): PENDING o DEPOSITED con vencimiento hastaDAYS_TO_EXPIRE(15) días.
📒 Asientos
Las clases de Lib/Accounting extienden AccountingClass del core. Comprueban que el ejercicio de la fecha exista y esté abierto, y guardan el asiento con editable = false. Si la cuenta bancaria no tiene subcuenta, usan la especial SPECIAL_PAYMENT_ACCOUNT.
- ReceiptGroupToAccounting, con sus hijas CustomerReceiptGroupToAccounting y SupplierReceiptGroupToAccounting: un asiento por agrupación con la fecha
groupdatee importetotal. Informaidentry.- Clientes: debe el banco por el total, haber la subcuenta de cada cliente (por recibo o agrupada). Por cada talón con cartera, debe cartera y haber banco.
- Proveedores: haber el banco por el total, debe la subcuenta de cada proveedor. Por cada talón con cartera, debe banco y haber cartera.
- Con agente: si tiene
codsubaccount_wallet, sustituye al banco en todas las líneas. Con diferencia, debecodsubaccount_differencey haber el banco o la cartera por-difference; sin esa subcuenta, la aprobación falla.
- BankCheckToAccounting: asientos del talón, con
documentoigual agetAccountingReference(). El sentido se invierte para los emitidos (instanceof CustomerBankCheck).charge(): agrupa por banco; los DEPOSITED van a suidbanky el resto al indicado. Genera un asiento por banco con una línea de banco y una de cartera por talón, e informaidentry,chargedeidbank. Con varios talones, el asiento no lleva referencia y el enlace es soloidentry.refund(): cartera contra banco más los gastos, que llevan su propia subcuenta. Vacíadepositedeidentry, así que el talón vuelve a PENDING.cancel(): cliente o proveedor (u otra subcuenta indicada) contra cartera. Pone RETURNED yreturned.- La empresa del ejercicio es la de la cuenta bancaria; sin cuenta, la de la agrupación o la empresa por defecto.
🧩 Extensiones y puntos de extensión
Se registran en Init::init().
- Extension/Model/ReciboCliente y ReciboProveedor: impiden borrar un recibo con
idmultipley normalizanidmultiplevacío a null. - Extension/Controller/ListFacturaCliente y ListFacturaProveedor: pestañas de agrupaciones y de talones, botones de lote y aviso de vencimientos.
- Extension/Controller/EditCliente y EditProveedor: pestaña de talones del cliente o proveedor.
- Extension/XMLView:
EditAgente(subcuentas del agente) yEditReciboCliente/EditReciboProveedor(enlace a la agrupación).
ReceiptGroupToAccounting usa ExtensionsTrait, y otro plugin puede añadir líneas al asiento de la agrupación con pipeFalse(). Si alguna extensión devuelve false, el asiento se descarta:
addHeaderLine($document, $entry, $line): tras la línea del banco.addLinesAgrupped($document, $entry, $receipts)yaddLinesWithoutGrouping($document, $entry, $receipt): tras las líneas de clientes o proveedores.addLines($document, $entry, $groupreceipts): tras todas las líneas de recibos.
🛠️ Migración
Migration/FillBankCheckStatus (pagosmultiples_fill_bankcheck_status_2026_10) se ejecuta una vez desde Init::update(). Rellena status en los talones de clientes anteriores a la 2.05 (CHARGED con charged, PENDING sin ella) y pone type = check donde estaba vacío. En el XML, type no tiene valor por defecto, para que la migración pueda distinguir los registros antiguos.