diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000000..ff5300ef481 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "python.languageServer": "None" +} \ No newline at end of file diff --git a/ecommerce/__init__.py b/ecommerce/__init__.py new file mode 100644 index 00000000000..9a7e03eded3 --- /dev/null +++ b/ecommerce/__init__.py @@ -0,0 +1 @@ +from . import models \ No newline at end of file diff --git a/ecommerce/__manifest__.py b/ecommerce/__manifest__.py new file mode 100644 index 00000000000..3829fae10e2 --- /dev/null +++ b/ecommerce/__manifest__.py @@ -0,0 +1,20 @@ +{ + 'name': 'E-commerce', + 'version': '1.0', + 'category': 'Website', + 'summary': 'E-commerce module for managing products and categories', + 'description': """ + This module provides basic e-commerce functionalities including product management and category organization. + """, + 'depends': "", + 'data': [ + 'security/ir.model.access.csv', + 'views/ecommerce_product_category_views.xml', + 'views/ecommerce_product_views.xml', + 'views/ecommerce_product_menus.xml', + + ], + 'installable': True, + 'application': True, + +} \ No newline at end of file diff --git a/ecommerce/models/__init__.py b/ecommerce/models/__init__.py new file mode 100644 index 00000000000..a7f4379f98e --- /dev/null +++ b/ecommerce/models/__init__.py @@ -0,0 +1,3 @@ +from . import ecommerce_product +from . import ecommerce_product_category +from . import ecommerce_product_offer \ No newline at end of file diff --git a/ecommerce/models/ecommerce_product.py b/ecommerce/models/ecommerce_product.py new file mode 100644 index 00000000000..16d564faf18 --- /dev/null +++ b/ecommerce/models/ecommerce_product.py @@ -0,0 +1,35 @@ +from odoo import fields, models,api + + +class EcommerceProduct(models.Model): + _name = 'ecommerce.product' + _description = 'E-commerce Product' + + title = fields.Char('title', required=True, help="Title of the product") + description = fields.Text('Description', help="Description of the product") + price = fields.Float('Price', required=True, help="Price of the product", default=0.0) + quantity = fields.Integer('Quantity', required=True, help="Available quantity of the product", default=0) + category_id = fields.Many2one( + comodel_name='ecommerce.product.category', + string='Category', + help="Category of the product", + ) + available = fields.Boolean( + string='Available', + compute='_compute_is_available', + store=True, + help="Indicates if the product is valid", + default=False,) + + + + + + + @api.depends('available', 'quantity') + def _compute_is_available(self): + for product in self: + if product.quantity > 0 : + product.available = True + + diff --git a/ecommerce/models/ecommerce_product_category.py b/ecommerce/models/ecommerce_product_category.py new file mode 100644 index 00000000000..ff33fbbde72 --- /dev/null +++ b/ecommerce/models/ecommerce_product_category.py @@ -0,0 +1,15 @@ +from odoo import models, fields + + +class EcommerceProductCategory(models.Model): + _name = "ecommerce.product.category" + _description = "E-commerce Product Category" + + name = fields.Char(string="Category Name", required=True) + description = fields.Text(string="Description") + product_id = fields.Many2one( + comodel_name="ecommerce.product", + string="Product Category", + ondelete="cascade", + ) + \ No newline at end of file diff --git a/ecommerce/models/ecommerce_product_offer.py b/ecommerce/models/ecommerce_product_offer.py new file mode 100644 index 00000000000..51cf1be6f80 --- /dev/null +++ b/ecommerce/models/ecommerce_product_offer.py @@ -0,0 +1,31 @@ +from odoo import models, fields + +class EcommerceProductOffer(models.Model): + _name = 'ecommerce.product.offer' + _description = 'Ecommerce Product Offer' + + name = fields.Char(string='Offer Name', required=True) + product_id = fields.Many2one('ecommerce.product', string='Product', required=True) + discount_percentage = fields.Float(string='Discount Percentage', required=True, help="Discount percentage for the offer") + start_date = fields.Date(string='Start Date', required=True) + end_date = fields.Date(string='End Date', required=True) + status = fields.Boolean(string='Active', default=True, help="Indicates if the offer is currently active") + + + def action_accept_offer(self): + if 'accepted' in self.mapped('product_id.offer_ids.status'): + raise ValueError("There is already an accepted offer for this product.") + self.write({'status': 'accepted'}) + return self.mapped('product_id').write({ + 'state': 'offer_accepted', + 'selling_price': self.product_id.price * (1 - self.discount_percentage / 100), + }) + + def action_reject_offer(self): + self.write({'status': 'rejected'}) + return self.mapped('product_id').write({ + 'state': 'offer_rejected', + }) + + + \ No newline at end of file diff --git a/ecommerce/security/ir.model.access.csv b/ecommerce/security/ir.model.access.csv new file mode 100644 index 00000000000..bc3544d770b --- /dev/null +++ b/ecommerce/security/ir.model.access.csv @@ -0,0 +1,3 @@ +id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink +access_ecommerce_product_user,access.ecommerce.product.user,model_ecommerce_product,base.group_user,1,1,1,1 +access_ecommerce_product_category_user,access.ecommerce.product.category.user,model_ecommerce_product_category,base.group_user,1,1,1,1 diff --git a/ecommerce/views/ecommerce_product_category_views.xml b/ecommerce/views/ecommerce_product_category_views.xml new file mode 100644 index 00000000000..1b7a4928159 --- /dev/null +++ b/ecommerce/views/ecommerce_product_category_views.xml @@ -0,0 +1,29 @@ + + + + + Products + ecommerce.product.category + tree,kanban,form + {'search_default_available': 1} + +

+ Create a product +

+

+ Create products and manage their details. +

+
+
+ + Products + ecommerce.product.category + + + + + + + +
+
\ No newline at end of file diff --git a/ecommerce/views/ecommerce_product_menus.xml b/ecommerce/views/ecommerce_product_menus.xml new file mode 100644 index 00000000000..56c2db270cb --- /dev/null +++ b/ecommerce/views/ecommerce_product_menus.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/ecommerce/views/ecommerce_product_offer_views.xml b/ecommerce/views/ecommerce_product_offer_views.xml new file mode 100644 index 00000000000..c4604b23d57 --- /dev/null +++ b/ecommerce/views/ecommerce_product_offer_views.xml @@ -0,0 +1,34 @@ + + + + + Products + ecommerce.product.offer + tree,kanban,form + {'search_default_available': 1} + +

+ Create a product offer +

+

+ Create product offers and manage their details. +

+
+
+ + ecommerce.product.offer.tree + ecommerce.product.offer + + + + + + + + + + + + + + + +
+
\ No newline at end of file diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml new file mode 100644 index 00000000000..9eacc51c77d --- /dev/null +++ b/estate/views/estate_property_views.xml @@ -0,0 +1,155 @@ + + + + + Property + estate.property + tree,kanban,form + {'search_default_available': 1} + +

+ Create a property advertisement +

+

+ Create real estate properties and follow the selling process. +

+
+
+ + Properties + estate.property + + + + + + + + + + + + + + + + + + + + + + + + + + + + estate.property.form + estate.property + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + estate.property.search + estate.property + + + + + + + + + + + + + + + + + + + + + estate.property.kanban + estate.property + + + + + +
+
+ + + +
+
+ Expected Price: +
+
+ Best Offer: +
+
+ Selling Price: +
+ +
+
+
+
+
+
+
+
\ No newline at end of file diff --git a/estate/views/res_users_views.xml b/estate/views/res_users_views.xml new file mode 100644 index 00000000000..63c4c9729a1 --- /dev/null +++ b/estate/views/res_users_views.xml @@ -0,0 +1,15 @@ + + + + res.users.form.inherit.estate + res.users + + + + + + + + + + \ No newline at end of file diff --git a/estate_account/__init__.py b/estate_account/__init__.py new file mode 100644 index 00000000000..9a7e03eded3 --- /dev/null +++ b/estate_account/__init__.py @@ -0,0 +1 @@ +from . import models \ No newline at end of file diff --git a/estate_account/__manifest__.py b/estate_account/__manifest__.py new file mode 100644 index 00000000000..b59feab2992 --- /dev/null +++ b/estate_account/__manifest__.py @@ -0,0 +1,11 @@ +{ + 'name': 'Estate Account', + 'version': '1.0', + 'application': True, + 'installable': True, + 'depends': [ + 'estate', + 'account', + ], + 'data': [], +} diff --git a/estate_account/models/__init__.py b/estate_account/models/__init__.py new file mode 100644 index 00000000000..5e1963c9d2f --- /dev/null +++ b/estate_account/models/__init__.py @@ -0,0 +1 @@ +from . import estate_property diff --git a/estate_account/models/estate_property.py b/estate_account/models/estate_property.py new file mode 100644 index 00000000000..dbe9aa4d88d --- /dev/null +++ b/estate_account/models/estate_property.py @@ -0,0 +1,41 @@ +from odoo import models, Command +from odoo.exceptions import UserError +import logging + +_logger = logging.getLogger(__name__) + +class EstateProperty(models.Model): + _inherit = "estate.property" + + def action_sold(self): + _logger.warning("🔥 estate_account: action_sold override triggered") + + journal = self.env["account.journal"].search([("type", "=", "sale")], limit=1) + if not journal: + raise UserError("No sale journal found.") + + for prop in self: + if not prop.buyer_id: + raise UserError(f"Property '{prop.name}' has no buyer.") + + invoice = self.env["account.move"].create({ + "partner_id": prop.buyer_id.id, + "move_type": "out_invoice", + "journal_id": journal.id, + "invoice_line_ids": [ + Command.create({ + "name": prop.name, + "quantity": 1.0, + "price_unit": prop.selling_price * 6.0 / 100.0, + }), + Command.create({ + "name": "Administrative fees", + "quantity": 1.0, + "price_unit": 100.0, + }), + ], + }) + + _logger.warning(f"✅ Invoice created for property '{prop.name}' → {invoice.name}") + + return super().action_sold()