diff --git a/addons/laundry_management/models/laundry_payment_method.py b/addons/laundry_management/models/laundry_payment_method.py new file mode 100644 index 0000000..f199212 --- /dev/null +++ b/addons/laundry_management/models/laundry_payment_method.py @@ -0,0 +1,101 @@ +from odoo import models, fields, api +from odoo.exceptions import UserError + + +class LaundryPaymentMethod(models.Model): + """Configurable payment method linked to an accounting journal. + + One record per payment option (e.g. Cash, Visa, Bank Transfer, Credit). + Each method is typed as cash / bank / credit so that session cash-control + and accounting routing work exactly like Odoo POS payment methods. + + cash → counted in the session cash drawer + bank → posted to a bank/card journal, not counted in cash + credit → deferred / no-posting (customer owes) + """ + _name = 'laundry.payment.method' + _description = 'Laundry Payment Method' + _order = 'sequence, id' + + # ── Identity ────────────────────────────────────────────────────── + name = fields.Char( + string='Method Name', + required=True, + translate=True, + ) + sequence = fields.Integer(default=10) + active = fields.Boolean(default=True) + company_id = fields.Many2one( + 'res.company', + default=lambda self: self.env.company, + ) + + # ── Type ────────────────────────────────────────────────────────── + payment_type = fields.Selection([ + ('cash', 'Cash / نقد'), + ('bank', 'Bank / Card / بنك'), + ('credit', 'Credit / Deferred / آجل'), + ], string='Type', required=True, default='cash', + help=( + 'cash → counts in session cash drawer\n' + 'bank → posted to bank/card journal\n' + 'credit → deferred, no immediate accounting entry' + ), + ) + + # ── Journal ─────────────────────────────────────────────────────── + journal_id = fields.Many2one( + 'account.journal', string='Accounting Journal', + domain="[('type', 'in', ['cash', 'bank']), ('company_id', '=', company_id)]", + help='Leave blank only for Credit/Deferred methods.', + ) + + # ── UI ──────────────────────────────────────────────────────────── + is_default = fields.Boolean( + string='Default', + help='Pre-selected when staff opens the Register Payment wizard.', + ) + + # ── Constraints ─────────────────────────────────────────────────── + @api.constrains('is_default', 'company_id') + def _check_single_default(self): + for rec in self.filtered('is_default'): + duplicate = self.search([ + ('is_default', '=', True), + ('company_id', '=', rec.company_id.id), + ('id', '!=', rec.id), + ], limit=1) + if duplicate: + raise UserError( + f'Only one default payment method is allowed per company.\n' + f'"{duplicate.name}" is already the default.\n' + 'Unset it first before marking this one as default.' + ) + + @api.constrains('payment_type', 'journal_id') + def _check_journal_required(self): + for rec in self: + if rec.payment_type in ('cash', 'bank') and not rec.journal_id: + raise UserError( + f'Payment method "{rec.name}" is of type "{rec.payment_type}" ' + 'and requires an accounting journal. ' + 'Please select a journal or change the type to Credit/Deferred.' + ) + + # ── Helpers ─────────────────────────────────────────────────────── + @api.model + def get_default_method(self): + """Return the default payment method for the current company.""" + company = self.env.company + return ( + self.search([ + ('is_default', '=', True), + ('company_id', '=', company.id), + ('active', '=', True), + ], limit=1) + or self.search([ + ('payment_type', '=', 'cash'), + ('company_id', '=', company.id), + ('active', '=', True), + ], limit=1) + )