diff --git a/estate/__init__.py b/Bakker/__init__.py similarity index 100% rename from estate/__init__.py rename to Bakker/__init__.py diff --git a/Bakker/__manifest__.py b/Bakker/__manifest__.py new file mode 100644 index 00000000000..724428aef15 --- /dev/null +++ b/Bakker/__manifest__.py @@ -0,0 +1,20 @@ +{ + "name": "Bakker", # The name that will appear in the App list + "version": "18.0.1.0.5", # Version + "application": True, # This line says the module is an App, and not a module + "depends": ["base", "mail"], # dependencies + "data": [ + "data/bakker_koeken_categorie_data.xml", + "data/bakker_koeken_tags_data.xml", + "data/bakker_koeken_data.xml", + "data/bakker_email_template.xml", + "security/ir.model.access.csv", + "views/bakker_koeken_categorie_views.xml", + "views/bakker_koeken_tags_views.xml", + "views/bakker_koeken_views.xml", + "views/bakker_verkoop_views.xml", + "reports/bakker_factuur_report.xml", + ], + "installable": True, + 'license': 'LGPL-3', +} diff --git a/Bakker/data/bakker_email_template.xml b/Bakker/data/bakker_email_template.xml new file mode 100644 index 00000000000..6c274001a2a --- /dev/null +++ b/Bakker/data/bakker_email_template.xml @@ -0,0 +1,97 @@ + + + + + + Bakkerij Factuur + + Factuur ${object.name} - Bakkerij + ${(object.env.user.email or '')|safe} + ${object.partner_id.email} + +
+
+

🍪 Bakkerij Factuur

+
+ +
+

Beste ${object.partner_id.name},

+ +

Bedankt voor uw aankoop bij onze bakkerij! Hier is uw factuur:

+ +
+

Factuur Details

+ + + + + + + + + + + + + + + + + + + + + + % if object.korting_percentage > 0: + + + + + % endif + + + + + + + + +
Factuurnummer:${object.name}
Datum:${format_datetime(object.verkoop_datum, tz=user.tz, dt_format='dd/MM/yyyy HH:mm')}
Product:${object.koek_id.name_koek}
Aantal:${object.aantal}
Prijs per stuk:€${object.prijs_per_stuk}
Korting (${object.korting_percentage}%):-€${object.korting_bedrag}
Totaal:€${object.totaal_bedrag}
Betaalmethode: + % if object.betaal_methode == 'cash': + Contant + % elif object.betaal_methode == 'card': + Bankkaart + % elif object.betaal_methode == 'digital': + Digitaal + % endif +
+
+ +
+

✅ Betaling Ontvangen

+

Uw betaling is succesvol verwerkt en ontvangen.

+
+ + % if object.opmerkingen: +
+

Opmerkingen:

+

${object.opmerkingen}

+
+ % endif + +

Dank u wel voor uw bezoek aan onze bakkerij! We hopen u snel weer te zien.

+ +
+

Met vriendelijke groet,
+ Het Bakkerij Team

+
+
+ + +
+
+
+
+
\ No newline at end of file diff --git a/Bakker/data/bakker_koeken_categorie_data.xml b/Bakker/data/bakker_koeken_categorie_data.xml new file mode 100644 index 00000000000..91f36e04304 --- /dev/null +++ b/Bakker/data/bakker_koeken_categorie_data.xml @@ -0,0 +1,35 @@ + + + + + + Chocolade + Koeken met chocolade + True + + + + Fruit + Koeken met fruit + True + + + + Noten + Koeken met noten + True + + + + Speculaas + Speculaas koeken + True + + + + Pudding + Koeken met pudding + True + + + \ No newline at end of file diff --git a/Bakker/data/bakker_koeken_data.xml b/Bakker/data/bakker_koeken_data.xml new file mode 100644 index 00000000000..e41e39b3ffc --- /dev/null +++ b/Bakker/data/bakker_koeken_data.xml @@ -0,0 +1,53 @@ + + + + + + Chocolade Brownie + 2.50 + 25 + 2025-09-26 + + True + + + + Chocolate Chip Cookie + 1.75 + 40 + 2025-09-28 + + False + + + + + Mini Appeltaart + 3.25 + 15 + 2025-09-24 + + False + + + + + Walnotenkoek + 2.95 + 20 + 2025-09-30 + + False + + + + + Traditionele Speculaas + 1.50 + 50 + 2025-10-01 + + False + + + \ No newline at end of file diff --git a/Bakker/data/bakker_koeken_tags_data.xml b/Bakker/data/bakker_koeken_tags_data.xml new file mode 100644 index 00000000000..68c17469ac4 --- /dev/null +++ b/Bakker/data/bakker_koeken_tags_data.xml @@ -0,0 +1,69 @@ + + + + + + Glutenvrij + 1 + + + + Suikervrij + 2 + + + + Vegan + 3 + + + + Biologisch + 4 + + + + Lactosevrij + 5 + + + + Vers + 6 + + + + Huisgemaakt + 7 + + + + Premium + 8 + + + + Seizoen + 9 + + + + Populair + 10 + + + + Krokant + 11 + + + + Zacht + 0 + + + Vers + 6 + + + \ No newline at end of file diff --git a/Bakker/models/__init__.py b/Bakker/models/__init__.py new file mode 100644 index 00000000000..3b5a0e3709d --- /dev/null +++ b/Bakker/models/__init__.py @@ -0,0 +1,4 @@ +from . import bakker_koeken +from . import bakker_koeken_tags +from . import bakker_koeken_categorie +from . import bakker_verkoop diff --git a/Bakker/models/bakker_koeken.py b/Bakker/models/bakker_koeken.py new file mode 100644 index 00000000000..670f0ee12fe --- /dev/null +++ b/Bakker/models/bakker_koeken.py @@ -0,0 +1,233 @@ +from odoo import models, fields, api +from datetime import timedelta +from odoo.exceptions import ValidationError + +class BakkerKoeken(models.Model): + _name = "bakker_koeken" + _description = "Bakker zijn lekkere koeken" + _order = "name_koek asc" + + name_koek = fields.Char(string="Naam van de koek", required=True, help="Vul hier de naam van de koek in") + prijs_koek = fields.Float(string="Prijs van de koek", required=True, help="Vul hier de prijs van de koek in") + voorraad_koek = fields.Integer(string="Voorraad van de koek", required=True , default=10) + vervaldatum_koek = fields.Date(string="Vervaldatum van de koek", required=True, help="Vul hier de vervaldatum van de koek in", default=lambda self: fields.Date.today() + timedelta(days=30), readonly=True) + aankoopdatum_koek = fields.Date(string="Aankoopdatum van de koek", default=fields.Date.today, help="Vul hier de aankoopdatum van de koek in", readonly=True) + categorie_koek_id = fields.Many2one('bakker_koeken_categorie', string="Categorie van de koek", required=True, help="Selecteer hier de categorie van de koek") + pudding_koek = fields.Boolean(string="Bevat pudding", default=False, help="Vink dit aan als de koek pudding bevat") + totaal_inventarisatie = fields.Float(string="Totale inventarisatie waarde", compute="_compute_totaal_inventarisatie", store=True, inverse="_inverse_totaal_inventarisatie", help="Totale waarde van de koek in inventarisatie (prijs * voorraad)") + tags_ids = fields.Many2many('bakker_koeken_tags', string="Tags", help="Selecteer hier de tags voor de koek") + + # Nieuwe verkoop gerelateerde velden + verkoop_ids = fields.One2many('bakker_verkoop', 'koek_id', string='Verkopen') + totaal_verkocht = fields.Integer(string='Totaal Verkocht', compute='_compute_verkoop_stats', store=True) + totaal_omzet = fields.Float(string='Totaal Omzet', compute='_compute_verkoop_stats', store=True) + verkoop_count = fields.Integer(string='Aantal Verkopen', compute='_compute_verkoop_count') + + @api.depends('verkoop_ids.aantal', 'verkoop_ids.totaal_bedrag', 'verkoop_ids.status') + def _compute_verkoop_stats(self): + for record in self: + betaalde_verkopen = record.verkoop_ids.filtered(lambda v: v.status == 'betaald') + record.totaal_verkocht = sum(betaalde_verkopen.mapped('aantal')) + record.totaal_omzet = sum(betaalde_verkopen.mapped('totaal_bedrag')) + + @api.depends('verkoop_ids') + def _compute_verkoop_count(self): + for record in self: + record.verkoop_count = len(record.verkoop_ids) + + @api.onchange('prijs_koek', 'voorraad_koek') + def _onchange_prijs_koek(self): + self._compute_totaal_inventarisatie() + + @api.onchange('totaal_inventarisatie') + def _onchange_totaal_inventarisatie(self): + self._inverse_totaal_inventarisatie() + + @api.depends('prijs_koek', 'voorraad_koek') + def _compute_totaal_inventarisatie(self): + for record in self: + record.totaal_inventarisatie = record.prijs_koek * record.voorraad_koek + + def _inverse_totaal_inventarisatie(self): + for record in self: + if record.prijs_koek != 0: + record.voorraad_koek = record.totaal_inventarisatie / record.prijs_koek + else: + record.voorraad_koek = 0 + + @api.constrains('voorraad_koek', 'prijs_koek') + def _check_non_negative(self): + for record in self: + if record.voorraad_koek < 0: + raise ValidationError("Voorraad van de koek kan niet negatief zijn.") + if record.prijs_koek < 0: + raise ValidationError("Prijs van de koek kan niet negatief zijn.") + + def action_voorraad_bijvullen(self): + """Vul voorraad bij met standaard hoeveelheid""" + for record in self: + record.voorraad_koek += 20 + record.aankoopdatum_koek = fields.Date.today() + + return { + 'type': 'ir.actions.client', + 'tag': 'reload', + 'params': { + 'title': 'Voorraad Bijgevuld!', + 'message': f'Voorraad van {self.name_koek} is bijgevuld met 20 stuks', + 'type': 'success', + } + } + + def action_uitverkocht(self): + """Markeer als uitverkocht""" + for record in self: + record.voorraad_koek = 0 + return True + + def action_verse_batch(self): + """Maak nieuwe verse batch - duplicate record""" + for record in self: + # Maak een kopie van het huidige record + new_batch = record.copy({ + 'voorraad_koek': 30, + 'aankoopdatum_koek': fields.Date.today(), + 'vervaldatum_koek': fields.Date.today() + timedelta(days=30), + 'name_koek': f"{record.name_koek} - Verse Batch", + }) + + vers_tag = self.env['bakker_koeken_tags'].search([('name', '=', 'Vers')], limit=1) + if vers_tag: + new_batch.tags_ids = [(4, vers_tag.id)] + + return { + 'type': 'ir.actions.act_window', + 'name': 'Nieuwe Verse Batch', + 'res_model': 'bakker_koeken', + 'res_id': new_batch.id, + 'view_mode': 'form', + 'target': 'current', + 'effect': { + 'fadeout': 'slow', + 'message': '🍪 Verse batch aangemaakt!', + 'type': 'rainbow_man', + } + } + + def action_mark_populair(self): + """Markeer als populair""" + populair_tag = self.env['bakker_koeken_tags'].search([('name', '=', 'Populair')], limit=1) + for record in self: + if populair_tag: + record.tags_ids = [(4, populair_tag.id)] + return True + + def action_seizoen_special(self): + """Markeer als seizoensspecial""" + seizoen_tag = self.env['bakker_koeken_tags'].search([('name', '=', 'Seizoen')], limit=1) + for record in self: + if seizoen_tag: + record.tags_ids = [(4, seizoen_tag.id)] + record.prijs_koek = record.prijs_koek * 1.15 # 15% prijsverhoging + return True + + def action_kwaliteitscontrole(self): + """Doe kwaliteitscontrole""" + import random + for record in self: + if random.choice([True, False]): # 50% kans + + vers_tag = self.env['bakker_koeken_tags'].search([('name', '=', 'Vers')], limit=1) + if vers_tag: + record.tags_ids = [(4, vers_tag.id)] + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'title': '✅ Kwaliteitscontrole Voltooid', + 'message': 'Kwaliteit gecontroleerd en goedgekeurd!', + 'type': 'info', + } + } + + def action_view_low_stock(self): + """Toon koeken met lage voorraad""" + action = self.env.ref('Bakker.bakker_koeken_action').read()[0] + action['domain'] = [('voorraad_koek', '<=', 10)] + action['context'] = {'search_default_filter_voorraad_laag': 1} + return action + + def action_verkoop_rapport(self): + """Genereer verkoop rapport""" + return { + 'name': 'Verkoop Rapport', + 'type': 'ir.actions.act_window', + 'res_model': 'bakker_koeken', + 'view_mode': 'pivot,graph', + 'context': { + 'group_by': ['categorie_koek_id'], + } + } + + def action_verkoop_koek(self): + """Open wizard om koeken te verkopen""" + return { + 'name': 'Koek Verkopen', + 'type': 'ir.actions.act_window', + 'res_model': 'bakker.verkoop.wizard', + 'view_mode': 'form', + 'target': 'new', + 'context': { + 'default_koek_id': self.id, + 'default_prijs_per_stuk': self.prijs_koek, + } + } + + def action_view_verkopen(self): + """Toon alle verkopen voor deze koek""" + return { + 'name': f'Verkopen voor {self.name_koek}', + 'type': 'ir.actions.act_window', + 'res_model': 'bakker_verkoop', + 'view_mode': 'list,form', + 'domain': [('koek_id', '=', self.id)], + 'context': {'default_koek_id': self.id} + } + + def action_snelle_verkoop(self): + """Snelle verkoop - direct 1 koek verkopen""" + if self.voorraad_koek <= 0: + raise ValidationError("Geen voorraad beschikbaar!") + + # Zoek of maak een standaard walk-in klant + walk_in_klant = self.env['res.partner'].search([('name', '=', 'Walk-in klant')], limit=1) + if not walk_in_klant: + walk_in_klant = self.env['res.partner'].create({ + 'name': 'Walk-in klant', + 'is_company': False, + 'customer_rank': 1, + 'street': 'Bakkerij', + 'email': 'walkin@bakkerij.local' + }) + + verkoop = self.env['bakker_verkoop'].create({ + 'koek_id': self.id, + 'partner_id': walk_in_klant.id, + 'aantal': 1, + 'prijs_per_stuk': self.prijs_koek, + 'betaal_methode': 'cash', + }) + + verkoop.action_bevestig_verkoop() + verkoop.action_markeer_betaald() + + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'title': '💰 Verkoop Voltooid!', + 'message': f'1x {self.name_koek} verkocht voor €{self.prijs_koek}', + 'type': 'success', + } + } + diff --git a/Bakker/models/bakker_koeken_categorie.py b/Bakker/models/bakker_koeken_categorie.py new file mode 100644 index 00000000000..8045719f18b --- /dev/null +++ b/Bakker/models/bakker_koeken_categorie.py @@ -0,0 +1,11 @@ +from odoo import models, fields + +class BakkerKoekenCategorie(models.Model): + _name = "bakker_koeken_categorie" + _description = "Categorieën voor bakker koeken" + _order = "name asc" + + + name = fields.Char(string="Categorie naam", required=True) + beschrijving = fields.Text(string="Beschrijving") + active = fields.Boolean(string="Actief", default=True) \ No newline at end of file diff --git a/Bakker/models/bakker_koeken_tags.py b/Bakker/models/bakker_koeken_tags.py new file mode 100644 index 00000000000..c7b8d4cd735 --- /dev/null +++ b/Bakker/models/bakker_koeken_tags.py @@ -0,0 +1,10 @@ +from odoo import models, fields + +class BakkerKoekenTags(models.Model): + _name = "bakker_koeken_tags" + _description = "Tags voor bakker koeken" + _order = "name asc" + + + name = fields.Char(string="Tag naam", required=True) + color = fields.Integer(string="Kleur", default=1) \ No newline at end of file diff --git a/Bakker/models/bakker_verkoop.py b/Bakker/models/bakker_verkoop.py new file mode 100644 index 00000000000..626dd87abe0 --- /dev/null +++ b/Bakker/models/bakker_verkoop.py @@ -0,0 +1,328 @@ +from odoo import models, fields, api +from odoo.exceptions import ValidationError +import base64 + +class BakkerVerkoop(models.Model): + _name = "bakker_verkoop" + _description = "Verkoop van bakkerij koeken" + _inherit = ['mail.thread', 'mail.activity.mixin'] + _order = "verkoop_datum desc" + + + name = fields.Char(string="Verkoop Nummer", required=True, default="Nieuw", tracking=True) + koek_id = fields.Many2one('bakker_koeken', string="Koek", required=True, tracking=True) + partner_id = fields.Many2one('res.partner', string="Klant", required=True, tracking=True) + klant_email = fields.Char(string="Email", related='partner_id.email', readonly=True) + klant_telefoon = fields.Char(string="Telefoon", related='partner_id.phone', readonly=True) + aantal = fields.Integer(string="Aantal", required=True, default=1, tracking=True) + prijs_per_stuk = fields.Float(string="Prijs per stuk", required=True, tracking=True) + korting_percentage = fields.Float(string="Korting (%)", default=0.0, tracking=True) + subtotaal = fields.Float(string="Subtotaal", compute="_compute_totalen", store=True) + korting_bedrag = fields.Float(string="Korting Bedrag", compute="_compute_totalen", store=True) + totaal_bedrag = fields.Float(string="Totaal Bedrag", compute="_compute_totalen", store=True, tracking=True) + verkoop_datum = fields.Datetime(string="Verkoop Datum", default=fields.Datetime.now, tracking=True) + status = fields.Selection([ + ('concept', 'Concept'), + ('bevestigd', 'Bevestigd'), + ('betaald', 'Betaald'), + ('geannuleerd', 'Geannuleerd') + ], string="Status", default='concept', tracking=True) + betaal_methode = fields.Selection([ + ('cash', 'Contant'), + ('card', 'Bankkaart'), + ('digital', 'Digitaal') + ], string="Betaal Methode", tracking=True) + opmerkingen = fields.Text(string="Opmerkingen") + + @api.model + def create(self, vals): + if vals.get('name', 'Nieuw') == 'Nieuw': + vals['name'] = self.env['ir.sequence'].next_by_code('bakker.verkoop') or f"VK{fields.Date.today().strftime('%Y%m%d')}-{len(self.search([]))}" + return super().create(vals) + + @api.depends('aantal', 'prijs_per_stuk', 'korting_percentage') + def _compute_totalen(self): + for record in self: + record.subtotaal = record.aantal * record.prijs_per_stuk + record.korting_bedrag = record.subtotaal * (record.korting_percentage / 100) + record.totaal_bedrag = record.subtotaal - record.korting_bedrag + + @api.constrains('aantal') + def _check_voorraad(self): + for record in self: + if record.status == 'concept' and record.aantal > record.koek_id.voorraad_koek: + raise ValidationError(f"Niet genoeg voorraad! Beschikbaar: {record.koek_id.voorraad_koek}") + + def action_bevestig_verkoop(self): + """Bevestig de verkoop en update voorraad""" + for record in self: + if record.status != 'concept': + raise ValidationError("Alleen concept verkopen kunnen bevestigd worden.") + + # Check voorraad nogmaals + if record.aantal > record.koek_id.voorraad_koek: + raise ValidationError(f"Niet genoeg voorraad! Beschikbaar: {record.koek_id.voorraad_koek}") + + # Update voorraad + record.koek_id.voorraad_koek -= record.aantal + record.status = 'bevestigd' + + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'title': '✅ Verkoop Bevestigd!', + 'message': f'Verkoop {self.name} is bevestigd en voorraad is bijgewerkt', + 'type': 'success', + } + } + + def action_markeer_betaald(self): + """Markeer als betaald en verstuur factuur email""" + for record in self: + if record.status != 'bevestigd': + raise ValidationError("Alleen bevestigde verkopen kunnen als betaald gemarkeerd worden.") + record.status = 'betaald' + + # Verstuur factuur email als klant een email adres heeft + if record.partner_id.email: + try: + # Zoek email template + template = self.env.ref('Bakker.email_template_bakker_factuur', raise_if_not_found=False) + if template: + # Verstuur email + template.send_mail(record.id, force_send=True) + + # Log activiteit (nu werkt message_post) + record.message_post( + body=f"Factuur email verstuurd naar {record.partner_id.email}", + subject="Factuur Email Verstuurd" + ) + except Exception as e: + # Log fout maar stop proces niet + record.message_post( + body=f"Fout bij versturen email: {str(e)}", + subject="Email Fout" + ) + + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'title': '💰 Betaling Ontvangen!', + 'message': f'Verkoop {self.name} is gemarkeerd als betaald' + (f' en factuur is verstuurd naar {self.partner_id.email}' if self.partner_id.email else ''), + 'type': 'success', + } + } + + def action_annuleer(self): + """Annuleer verkoop en herstel voorraad""" + for record in self: + if record.status == 'betaald': + raise ValidationError("Betaalde verkopen kunnen niet geannuleerd worden.") + + # Herstel voorraad als verkoop bevestigd was + if record.status == 'bevestigd': + record.koek_id.voorraad_koek += record.aantal + + record.status = 'geannuleerd' + + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'title': '❌ Verkoop Geannuleerd', + 'message': f'Verkoop {self.name} is geannuleerd en voorraad is hersteld', + 'type': 'warning', + } + } + + def action_print_factuur(self): + """Print factuur PDF""" + return self.env.ref('Bakker.report_bakker_factuur').report_action(self) + + def action_open_print_wizard(self): + """Open standaard print wizard voor factuur""" + return { + 'type': 'ir.actions.act_window', + 'name': 'Print Opties', + 'res_model': 'base.document.layout', + 'view_mode': 'form', + 'target': 'new', + 'context': { + 'default_report_template': 'web.external_layout_standard', + 'active_ids': self.ids, + 'active_model': 'bakker_verkoop', + 'report_action': self.env.ref('Bakker.report_bakker_factuur').id, + } + } + + def action_configure_print(self): + """Open document layout configuratie voor factuur""" + return { + 'type': 'ir.actions.act_window', + 'name': 'Configure Document Layout', + 'res_model': 'base.document.layout', + 'view_mode': 'form', + 'target': 'new', + 'context': { + 'default_report_layout': 'web.external_layout_standard', + 'default_logo': True, + 'default_company_details': True, + 'active_ids': self.ids, + 'active_model': 'bakker_verkoop', + 'report_action': self.env.ref('Bakker.report_bakker_factuur').id, + } + } + + def action_print_with_layout(self): + """Print factuur met document layout opties""" + return { + 'type': 'ir.actions.act_window', + 'name': 'Print Factuur', + 'res_model': 'base.document.layout', + 'view_mode': 'form', + 'target': 'new', + 'context': { + 'default_report_layout': 'web.external_layout_standard', + 'default_logo': True, + 'default_company_details': True, + 'active_ids': self.ids, + 'active_model': 'bakker_verkoop', + 'report_action': self.env.ref('Bakker.report_bakker_factuur').report_action(self), + 'close_on_report_download': True, + } + } + + +class BakkerVerkoopWizard(models.TransientModel): + _name = 'bakker.verkoop.wizard' + _description = 'Wizard voor het verkopen van koeken' + + koek_id = fields.Many2one('bakker_koeken', string='Koek', required=True) + partner_id = fields.Many2one('res.partner', string='Klant', required=True) + aantal = fields.Integer(string='Aantal', required=True, default=1) + prijs_per_stuk = fields.Float(string='Prijs per stuk', required=True) + korting_percentage = fields.Float(string='Korting (%)', default=0.0) + finale_prijs = fields.Float(string='Finale Prijs per stuk', compute='_compute_finale_prijs') + totaal_bedrag = fields.Float(string='Totaal Bedrag', compute='_compute_totaal_bedrag') + beschikbare_voorraad = fields.Integer(related='koek_id.voorraad_koek', string='Beschikbare Voorraad') + direct_betaald = fields.Boolean(string='Direct Betaald', default=True) + betaal_methode = fields.Selection([ + ('cash', 'Contant'), + ('card', 'Bankkaart'), + ('digital', 'Digitaal') + ], string="Betaal Methode", default='cash') + + # Gerelateerde velden van de klant + klant_email = fields.Char(string="Email", related='partner_id.email', readonly=True) + klant_telefoon = fields.Char(string="Telefoon", related='partner_id.phone', readonly=True) + klant_adres = fields.Char(string="Adres", related='partner_id.street', readonly=True) + klant_stad = fields.Char(string="Stad", related='partner_id.city', readonly=True) + + @api.depends('prijs_per_stuk', 'korting_percentage') + def _compute_finale_prijs(self): + for record in self: + if record.korting_percentage: + record.finale_prijs = record.prijs_per_stuk * (1 - record.korting_percentage / 100) + else: + record.finale_prijs = record.prijs_per_stuk + + @api.depends('aantal', 'finale_prijs') + def _compute_totaal_bedrag(self): + for record in self: + record.totaal_bedrag = record.aantal * record.finale_prijs + + @api.constrains('aantal') + def _check_voorraad(self): + for record in self: + if record.aantal > record.koek_id.voorraad_koek: + raise ValidationError(f"Niet genoeg voorraad! Beschikbaar: {record.koek_id.voorraad_koek}") + + def action_verkoop(self): + """Voer de verkoop uit""" + verkoop = self.env['bakker_verkoop'].create({ + 'koek_id': self.koek_id.id, + 'partner_id': self.partner_id.id, + 'aantal': self.aantal, + 'prijs_per_stuk': self.finale_prijs, + 'korting_percentage': self.korting_percentage, + 'betaal_methode': self.betaal_methode, + }) + + # Bevestig verkoop + verkoop.action_bevestig_verkoop() + + # Markeer als betaald indien gewenst + if self.direct_betaald: + verkoop.action_markeer_betaald() + + return { + 'name': 'Nieuwe Verkoop', + 'type': 'ir.actions.act_window', + 'res_model': 'bakker_verkoop', + 'res_id': verkoop.id, + 'view_mode': 'form', + 'target': 'current', + } + + def action_create_new_customer(self): + """Maak een nieuwe klant aan""" + return { + 'name': 'Nieuwe Klant', + 'type': 'ir.actions.act_window', + 'res_model': 'res.partner', + 'view_mode': 'form', + 'target': 'new', + 'context': {'default_is_company': False, 'default_customer_rank': 1} + } + +class BakkerFactuurWizard(models.TransientModel): + _name = 'bakker.factuur.wizard' + _description = 'Factuur Preview Wizard' + + verkoop_id = fields.Many2one('bakker_verkoop', string='Verkoop', required=True) + pdf_data = fields.Binary(string='PDF Data') + pdf_filename = fields.Char(string='Filename') + show_preview = fields.Boolean(string='Toon Preview', default=True) + + def action_preview_factuur(self): + """Genereer PDF preview""" + report = self.env.ref('Bakker.report_bakker_factuur') + pdf_content, _ = report._render_qweb_pdf([self.verkoop_id.id]) + + self.pdf_data = base64.b64encode(pdf_content) + self.pdf_filename = f"Factuur_{self.verkoop_id.name}.pdf" + self.show_preview = True + + return { + 'type': 'ir.actions.act_window', + 'name': 'Factuur Preview', + 'res_model': 'bakker.factuur.wizard', + 'res_id': self.id, + 'view_mode': 'form', + 'target': 'new', + 'context': {'show_preview': True} + } + + def action_download_factuur(self): + """Download de PDF""" + if not self.pdf_data: + self.action_preview_factuur() + + return { + 'type': 'ir.actions.act_url', + 'url': f'/web/content?model=bakker.factuur.wizard&id={self.id}&field=pdf_data&filename_field=pdf_filename&download=true', + 'target': 'self', + } + + def action_print_factuur(self): + """Print de PDF via browser""" + if not self.pdf_data: + self.action_preview_factuur() + + return { + 'type': 'ir.actions.act_url', + 'url': f'/web/content?model=bakker.factuur.wizard&id={self.id}&field=pdf_data&filename_field=pdf_filename', + 'target': 'new', + } \ No newline at end of file diff --git a/Bakker/reports/bakker_factuur_report.xml b/Bakker/reports/bakker_factuur_report.xml new file mode 100644 index 00000000000..7fe8e6be6ce --- /dev/null +++ b/Bakker/reports/bakker_factuur_report.xml @@ -0,0 +1,183 @@ + + + + + + Bakkerij Factuur + bakker_verkoop + qweb-pdf + Bakker.report_bakker_factuur_document + Bakker.report_bakker_factuur_document + + report + + 'Factuur_' + object.name + False + False + + + + + + \ No newline at end of file diff --git a/Bakker/security/ir.model.access.csv b/Bakker/security/ir.model.access.csv new file mode 100644 index 00000000000..4c9eea86b76 --- /dev/null +++ b/Bakker/security/ir.model.access.csv @@ -0,0 +1,6 @@ +id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink +bakker_koeken,access_bakker_koeken,model_bakker_koeken,base.group_user,1,1,1,1 +bakker_koeken_tags,access_bakker_koeken_tags,model_bakker_koeken_tags,base.group_user,1,1,1,1 +bakker_koeken_categorie,access_bakker_koeken_categorie,model_bakker_koeken_categorie,base.group_user,1,1,1,1 +bakker_verkoop,access_bakker_verkoop,model_bakker_verkoop,base.group_user,1,1,1,1 +bakker_verkoop_wizard,access_bakker_verkoop_wizard,model_bakker_verkoop_wizard,base.group_user,1,1,1,1 \ No newline at end of file diff --git a/Bakker/views/bakker_koeken_categorie_views.xml b/Bakker/views/bakker_koeken_categorie_views.xml new file mode 100644 index 00000000000..fe27b1717dc --- /dev/null +++ b/Bakker/views/bakker_koeken_categorie_views.xml @@ -0,0 +1,57 @@ + + + + + + bakker.koeken.categorie.list + bakker_koeken_categorie + + + + + + + + + + + + bakker.koeken.categorie.form + bakker_koeken_categorie + +
+ + + + + + + + + +
+
+
+ + + + Koeken Categorieën + bakker_koeken_categorie + list,form + +

+ Maak uw eerste koeken categorie aan! +

+

+ Categorieën helpen bij het organiseren van uw koeken. +

+
+
+ + + +
+
\ No newline at end of file diff --git a/Bakker/views/bakker_koeken_tags_views.xml b/Bakker/views/bakker_koeken_tags_views.xml new file mode 100644 index 00000000000..dd10caaba8a --- /dev/null +++ b/Bakker/views/bakker_koeken_tags_views.xml @@ -0,0 +1,45 @@ + + + + + + bakker.koeken.tags.list + bakker_koeken_tags + + + + + + + + + + + bakker.koeken.tags.form + bakker_koeken_tags + +
+ + + + + + +
+
+
+ + + + Koeken Tags + bakker_koeken_tags + list,form + + + + +
+
\ No newline at end of file diff --git a/Bakker/views/bakker_koeken_views.xml b/Bakker/views/bakker_koeken_views.xml new file mode 100644 index 00000000000..3ab4c8bbe06 --- /dev/null +++ b/Bakker/views/bakker_koeken_views.xml @@ -0,0 +1,285 @@ + + + + + bakker.koeken.search + bakker_koeken + + + + + + + + + + + + + + + + + + Bakker Koeken + bakker_koeken + kanban,list,form + + +

+ Create the first Bakker Koeken +

+
+
+ + + + + + + + + bakker.koeken.kanban + bakker_koeken + + + + + + + + + + + + + + + + +
+ +
+
+ + + +
+ € +
+
+
+ + + +
+
+ + +
+ +
+ + + 📦 op voorraad + + + + + ⚠️ resterend + + + + + ❌ Uitverkocht + + +
+ + +
+ 🍮 Pudding +
+ + +
+ +
+ + +
+ + Vervalt: + +
+ + +
+
+ + 💰 Verkocht: + +
+
+ + 💵 € + +
+
+
+ + +
+
+
+ + 📊 verkopen + +
+
+
+ + + +
+
+ + + +
+
+
+
+
+
+ + + + bakker.koeken.list + bakker_koeken + + + + + + + + + + + + + + + + + + + + bakker.koeken.form + bakker_koeken + +
+
+ +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
\ No newline at end of file diff --git a/Bakker/views/bakker_verkoop_views.xml b/Bakker/views/bakker_verkoop_views.xml new file mode 100644 index 00000000000..b64bccb4ce5 --- /dev/null +++ b/Bakker/views/bakker_verkoop_views.xml @@ -0,0 +1,177 @@ + + + + + + bakker.verkoop.list + bakker_verkoop + + + + + + + + + + + + + + + + + + + bakker.verkoop.form + bakker_verkoop + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+
+
+ + + + bakker.verkoop.wizard.form + bakker.verkoop.wizard + +
+ + + + + + + + + + + + + +
+
+
+
+
+ + + + bakker.factuur.wizard.form + bakker.factuur.wizard + +
+ +
+

📄 Factuur Preview

+

Bekijk de factuur voordat je download of print

+
+ + + + + + + +
+
Factuur Preview:
+ +
+ + + +
+ +
+
+
+
+
+ + + + Verkopen + bakker_verkoop + list,form + + + + +
+
\ No newline at end of file diff --git a/estate/__manifest__.py b/estate/__manifest__.py deleted file mode 100644 index d7cfa57d4c5..00000000000 --- a/estate/__manifest__.py +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "Estate", # The name that will appear in the App list - "version": "16.0.0", # Version - "application": True, # This line says the module is an App, and not a module - "depends": ["base"], # dependencies - "data": [ - - ], - "installable": True, - 'license': 'LGPL-3', -} diff --git a/estate/models.py b/estate/models.py deleted file mode 100644 index e69de29bb2d..00000000000