@ -0,0 +1,45 @@ |
|||
.. image:: https://img.shields.io/badge/license-LGPL--3-green.svg |
|||
:target: https://www.gnu.org/licenses/lgpl-3.0-standalone.html |
|||
:alt: License: LGPL-3 |
|||
|
|||
Product Bidding In ECommerce |
|||
============================ |
|||
This module helps you to add auction in website sale. |
|||
|
|||
Configuration |
|||
============= |
|||
* No additional configurations needed |
|||
|
|||
Company |
|||
------- |
|||
* `Cybrosys Techno Solutions <https://cybrosys.com/>`__ |
|||
|
|||
License |
|||
------- |
|||
Lesser General Public License, Version 3 (LGPL v3). |
|||
(http://www.gnu.org/licenses/lgpl-3.0-standalone.html) |
|||
|
|||
Credits |
|||
------- |
|||
* Developers: (V17) Anfas Faisal K, Contact:odoo@cybrosys.com |
|||
(V18) Ranjith R, Contact:odoo@cybrosys.com |
|||
|
|||
Contacts |
|||
-------- |
|||
* Mail Contact : odoo@cybrosys.com |
|||
* Website : https://cybrosys.com |
|||
|
|||
Bug Tracker |
|||
----------- |
|||
Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. |
|||
|
|||
Maintainer |
|||
========== |
|||
.. image:: https://cybrosys.com/images/logo.png |
|||
:target: https://cybrosys.com |
|||
This module is maintained by Cybrosys Technologies. |
|||
For support and more information, please visit `Our Website <https://cybrosys.com/>`__ |
|||
|
|||
Further information |
|||
=================== |
|||
HTML Description: `<static/description/index.html>`__ |
@ -0,0 +1,23 @@ |
|||
# -*- coding: utf-8 -*- |
|||
############################################################################# |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
|||
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>) |
|||
# |
|||
# You can modify it under the terms of the GNU LESSER |
|||
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. |
|||
# |
|||
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE |
|||
# (LGPL v3) along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
############################################################################# |
|||
from . import controllers |
|||
from . import models |
@ -0,0 +1,60 @@ |
|||
# -*- coding: utf-8 -*- |
|||
############################################################################# |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
|||
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>) |
|||
# |
|||
# You can modify it under the terms of the GNU LESSER |
|||
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. |
|||
# |
|||
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE |
|||
# (LGPL v3) along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
############################################################################# |
|||
{ |
|||
'name': 'Product Bidding In ECommerce', |
|||
'version': '18.0.1.0.0', |
|||
'category': 'Website', |
|||
'summary': 'App To Add Bidding option In Website', |
|||
'description': 'Website Bargain is an application where users can create ' |
|||
'and manage bargains on their website, enabling their ' |
|||
'customers to participate in bidding and negotiate prices ' |
|||
'for products or services', |
|||
'author': 'Cybrosys Techno Solution', |
|||
'company': 'Cybrosys Techno Solutions', |
|||
'maintainer': 'Cybrosys Techno Solution', |
|||
'website': 'https://www.cybrosys.com', |
|||
'depends': ['website_sale', 'contacts'], |
|||
'data': [ |
|||
'security/ir.model.access.csv', |
|||
'data/ir_cron_data.xml', |
|||
'data/mail_template_data.xml', |
|||
'views/website_bargain_views.xml', |
|||
'views/bargain_template_views.xml', |
|||
'views/bargain_information_views.xml', |
|||
'views/bargain_subscribers_views.xml', |
|||
'views/product_template_views.xml', |
|||
'views/website_shop_auction_templates.xml', |
|||
'views/website_product_views_templates.xml', |
|||
'views/website_bargain_menus.xml', |
|||
'views/bidders_information_templates.xml' |
|||
], |
|||
'assets': { |
|||
'web.assets_frontend': [ |
|||
'website_bargain/static/src/js/website_bargain.js', |
|||
'website_bargain/static/src/css/website_product_bargain.css', |
|||
]}, |
|||
'images': ['static/description/banner.jpg'], |
|||
'license': 'LGPL-3', |
|||
'installable': True, |
|||
'auto-install': False, |
|||
'application': False, |
|||
} |
@ -0,0 +1,22 @@ |
|||
# -*- coding: utf-8 -*- |
|||
############################################################################# |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
|||
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>) |
|||
# |
|||
# You can modify it under the terms of the GNU LESSER |
|||
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. |
|||
# |
|||
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE |
|||
# (LGPL v3) along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
############################################################################# |
|||
from . import website_bargain |
@ -0,0 +1,353 @@ |
|||
# -*- coding: utf-8 -*- |
|||
############################################################################# |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
|||
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>) |
|||
# |
|||
# You can modify it under the terms of the GNU LESSER |
|||
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. |
|||
# |
|||
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE |
|||
# (LGPL v3) along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
############################################################################# |
|||
from odoo import fields, http |
|||
from odoo.addons.website_sale.controllers.main import WebsiteSale |
|||
from odoo.http import request |
|||
|
|||
|
|||
class WebsiteAuction(WebsiteSale): |
|||
"""This class enables is used to view the timer and related functions |
|||
of bidding,buy now and other routes |
|||
_prepare_product_values(self, product, category, search, **kwargs): |
|||
This function is inbuilt function in website sale and its |
|||
supered here and pass auction values in this function |
|||
auction_timer(self, auction_id): |
|||
This function gets the auction id and pass auction timing |
|||
details to website |
|||
buy_now(self, auction_id, product_id): |
|||
Its the function for button in buy now,in this function a new |
|||
sale order is created and send to the customer through email |
|||
def auction_close(self, auction_id): |
|||
Function gets the auction id and move the auction stage to |
|||
finished and make the product unavailable from website |
|||
winner_sale_order(self, auction_id, **post): |
|||
Makes a sale order to the winner and id notifications are |
|||
enabled it will send a email to the winners or losers |
|||
subscribe(self, auction_id, **post): |
|||
Used to add subscribers from website to database |
|||
place_bid(self, auction_id, **post): |
|||
When place bid button is triggered it will call this function |
|||
and the bidders details will be added to database and |
|||
if enabled it will send notification to subscribers |
|||
bidders(self, product_id): |
|||
Route to pass bidders details to website |
|||
bid_cancel(self, bidders): |
|||
Function to cancel the bid""" |
|||
|
|||
def _prepare_product_values(self, product, category, search, **kwargs): |
|||
"""Summary: |
|||
Function is used to pass auction details by supering |
|||
this function because its already defined it websitesale |
|||
Args: |
|||
product:pass the object product |
|||
category:pass the category if enabled |
|||
kwargs:used to pass variable to function |
|||
Return: |
|||
Values is updated with auction and current website and |
|||
passed to website sale""" |
|||
values = super()._prepare_product_values(product, category, search, |
|||
**kwargs) |
|||
current_website = request.website |
|||
auction = request.env['website.bargain'].sudo().search_read([]) |
|||
values.update({ |
|||
'auction': auction, |
|||
'website': current_website |
|||
}) |
|||
return values |
|||
|
|||
@http.route('/auction/timer', type='json', auth='user', csrf=False) |
|||
def auction_timer(self, auction_id): |
|||
"""Summary: |
|||
Function to pass timer details from auction like start |
|||
time,end time,extended time |
|||
Args: |
|||
auction_id:used to get auction id from website |
|||
Return: |
|||
Values which include start_time,end time,extended |
|||
time and state id""" |
|||
auction = request.env['website.bargain'].sudo().browse(int(auction_id)) |
|||
values = { |
|||
'start_time': auction.start_time, |
|||
'end_time': fields.Datetime.context_timestamp(auction, |
|||
auction.end_time), |
|||
'state': auction.state, |
|||
} |
|||
if auction.extend_time: |
|||
values['extend_time'] = fields.Datetime.context_timestamp(auction, |
|||
auction.extend_time) |
|||
return values |
|||
|
|||
@http.route('/auction/close', type='json', auth='user', csrf=False) |
|||
def auction_close(self, auction_id): |
|||
"""Summary: |
|||
Function to close auction when a customer buys |
|||
a product instantly or the auction is ended |
|||
Args: |
|||
auction_id: used to get auction id from website |
|||
Return: |
|||
a string auction ended to website""" |
|||
auction = request.env['website.bargain'].sudo().browse(int(auction_id)) |
|||
auction.product_id.is_auction = False |
|||
auction.product_id.is_published = False |
|||
auction.write({'state': 'finished'}) |
|||
return "auction ended" |
|||
|
|||
@http.route('/place_bid', type='json', auth='user', csrf=False) |
|||
def place_bid(self, auction_id, **post): |
|||
"""Summary: |
|||
When place bid button is clicked it will trigger this function |
|||
and will add bidders details to backend and will send |
|||
notification to the subscribers if its enabled |
|||
Args: |
|||
auction_id:used to get auction id from website |
|||
post:used to get bid amount from frontend |
|||
Return: |
|||
A message bid placed and suction details""" |
|||
bid_amount = post.get('bid_amount') |
|||
auction = request.env['website.bargain'].sudo().browse(int(auction_id)) |
|||
bidder_total = auction.bargain_information_ids.search_count( |
|||
[('bidder_id', '=', request.env.user.partner_id.id), |
|||
('bid_offer', '=', bid_amount)]) |
|||
if bidder_total >= 1: |
|||
return False |
|||
else: |
|||
auction.bargain_information_ids.sudo().create({ |
|||
'bidder_id': request.env.user.partner_id.id, |
|||
'auction_id': auction.id, |
|||
'bid_offer': bid_amount, |
|||
'status': 'Bid Placed' |
|||
}) |
|||
if auction.is_subscriber_start_notification and not auction.is_send_mail: |
|||
data = 'Bidding has started for ' + auction.name + '. Please check our website for more details.' |
|||
subscribers = "" |
|||
for record in auction.bargain_subscribers_ids: |
|||
subscribers += record.subscriber_id.email + ',' |
|||
email_values = {'email_from': auction.auction_manager_id.email, |
|||
'subject': 'Bidding Started for ' + auction.name, |
|||
'email_to': subscribers, 'body_html': data} |
|||
request.env['mail.mail'].sudo().create(email_values).send() |
|||
auction.is_send_mail = True |
|||
auction.write({'is_send_mail': True}) |
|||
|
|||
if auction.is_new_bid_notification: |
|||
data = 'A new bid has been placed with amount ' + str( |
|||
bid_amount) + ',on ' \ |
|||
+ auction.name + ',by ' + request.env.user.partner_id.name \ |
|||
+ 'please check into ' \ |
|||
'our website' |
|||
subscribers = " " |
|||
for record in auction.bargain_subscribers_ids: |
|||
subscribers += record.subscriber_id.email + ',' |
|||
email_values = {'email_from': auction.auction_manager_id.email, |
|||
'subject': 'New Bid Placed on ' + auction.name, |
|||
'email_to': subscribers, 'body_html': data} |
|||
request.env['mail.mail'].sudo().create(email_values).send() |
|||
values = ({ |
|||
'bid_placed': 'Bid Placed', |
|||
'auction': auction |
|||
}) |
|||
|
|||
return values |
|||
|
|||
@http.route('/bidders/<model("product.template"):product_id>', type='http', |
|||
auth='user', csrf=False, website=True) |
|||
def bidders(self, product_id): |
|||
""" |
|||
This method is used to fetch the details of the bidders participating in |
|||
an auction and render them on the website. |
|||
Args: |
|||
product_id: A product template model object. |
|||
Returns: |
|||
A rendered HTML template with information about the bidders in the |
|||
auction.""" |
|||
auction = request.env['website.bargain'].sudo().search( |
|||
[('product_id', '=', product_id.id), ('state', '=', 'running')]) |
|||
bidders_information = request.env['bargain.information'].sudo().search( |
|||
[('auction_id', '=', auction.id)], |
|||
order='bid_offer desc') |
|||
values = ({ |
|||
'bidders_information': bidders_information, |
|||
'product_id': product_id, |
|||
}) |
|||
return request.render('website_bargain.bidders_information', values) |
|||
|
|||
@http.route('/bid/cancel/<model("bargain.information"):bidders>', |
|||
type='http', auth='user', csrf='false') |
|||
def bid_cancel(self, bidders): |
|||
"""This function is for cancel the bid |
|||
Args: |
|||
bidders:Bidders name will be getting here |
|||
Returns:It returns previous page""" |
|||
bidders.status = 'cancelled' |
|||
previous_web_url = request.httprequest.headers.get('referer') |
|||
return request.redirect(previous_web_url) |
|||
|
|||
@http.route('/shop/sale/order', type='json', auth='public', website=True, |
|||
csrf=False) |
|||
def winner_sale_order(self, auction_id, **post): |
|||
""" |
|||
This route creates a draft sale order for the winner of an auction |
|||
and sends notifications for winning and losing. |
|||
Args: |
|||
auction_id: the ID of the auction obtained from the frontend |
|||
post: used to get data of product from frontend |
|||
Returns: |
|||
True""" |
|||
# Get the ID of the product from the post data |
|||
product_product_id = post.get('product_product_id') |
|||
# Find the auction with the given ID |
|||
auction = request.env['website.bargain'].sudo().browse(int(auction_id)) |
|||
auction.write({'state': 'finished'}) |
|||
if auction.is_bid_end_notification: |
|||
data = 'Bidding has ended for ' + auction.name + '. Thank you for participating.' |
|||
subscribers = "" |
|||
for record in auction.bargain_subscribers_ids: |
|||
subscribers += record.subscriber_id.email + ',' |
|||
email_values = {'email_from': auction.auction_manager_id.email, |
|||
'subject': 'Bidding Ended for ' + auction.name, |
|||
'email_to': subscribers, 'body_html': data} |
|||
request.env['mail.mail'].sudo().create(email_values).send() |
|||
# Find the highest bidder for the auction |
|||
bid_record = auction.bargain_information_ids.filtered( |
|||
lambda r: r.status == 'Bid Placed').sorted( |
|||
key=lambda r: r.bid_offer)[-1] |
|||
# Create a draft sale order for the winner |
|||
sale_order = request.env['sale.order'].sudo().create({ |
|||
'partner_id': bid_record.bidder_id.id, |
|||
'state': 'draft', |
|||
}) |
|||
# Add the product to the sale order and set the price to the bid offer |
|||
sale_order.sudo().write({ |
|||
'order_line': [(0, 0, { |
|||
'product_id': int(product_product_id), |
|||
'product_uom_qty': 1, |
|||
'price_unit': bid_record.bid_offer, |
|||
'name': 'auction won', |
|||
})] |
|||
}) |
|||
mail_compose_message = request.env['mail.compose.message'] |
|||
so_mcm_vals = sale_order.sudo().action_quotation_send().get('context', |
|||
{}) |
|||
compose_msg = mail_compose_message.sudo().with_context( |
|||
so_mcm_vals).create({}) |
|||
compose_msg.sudo().action_send_mail() |
|||
# Send a notification email to the winner and/or losers, if enabled |
|||
if auction.is_winner_notification: |
|||
data = "You have won in " + auction.name + " Kindly pay now and" \ |
|||
" collect the product " \ |
|||
"from our website" |
|||
email_values = { |
|||
'email_from': auction.auction_manager_id.email, |
|||
'subject': 'Won ' + auction.name, |
|||
'email_to': bid_record.bidder_id.email, |
|||
'body_html': data |
|||
} |
|||
request.env['mail.mail'].sudo().create(email_values).send() |
|||
if auction.is_loser_notification: |
|||
data = "You have lost in " + auction.name + "Better luck next time"\ |
|||
" thank you for your " \ |
|||
"effort and time" |
|||
email_to = '' |
|||
for record in auction.bargain_information_ids: |
|||
if record.bidder_id.id != bid_record.bidder_id.id: |
|||
email_to += record.bidder_id.email + ',' |
|||
email_values = { |
|||
'email_from': auction.auction_manager_id.email, |
|||
'subject': 'Lost ' + auction.name, |
|||
'email_to': email_to, |
|||
'body_html': data |
|||
} |
|||
request.env['mail.mail'].sudo().create(email_values).send() |
|||
return True |
|||
|
|||
@http.route('/subscribe/bid', type='json', auth='user', csrf=False) |
|||
def subscribe(self, auction_id, **post): |
|||
""" |
|||
Endpoint to manage subscription to an auction |
|||
auction_id: integer ID of the auction being subscribed to/unsubscribed |
|||
text: string indicating whether the user is subscribing or |
|||
unsubscribing ('subscribe' or 'unsubscribe') |
|||
return: string message indicating success or failure of |
|||
subscription/unsubscription action |
|||
""" |
|||
text = post.get('text') |
|||
auction = request.env['website.bargain'].sudo().browse(int(auction_id)) |
|||
if text == 'subscribe': |
|||
if request.env.user.partner_id in \ |
|||
auction.bargain_subscribers_ids.subscriber_id: |
|||
return 'You have already subscribed' |
|||
auction.bargain_subscribers_ids.sudo().create({ |
|||
'subscriber_id': request.env.user.partner_id.id, |
|||
'auction_id': auction.id, |
|||
'is_subscribed': True |
|||
}) |
|||
return 'You have been successfully subscribed to this auction' |
|||
elif text == 'unsubscribe': |
|||
auction.bargain_subscribers_ids.sudo().search( |
|||
[('subscriber_id', '=', request.env.user.partner_id.id), |
|||
('auction_id', '=', auction.id)]).unlink() |
|||
return 'Unsubscribed successfully' |
|||
|
|||
@http.route('/subscribe/status', type='json', auth='user', csrf=False) |
|||
def subscribe_status(self, auction_id, **post): |
|||
""" |
|||
Controller method to handle subscription to an auction. |
|||
""" |
|||
auction = request.env['website.bargain'].sudo().browse(int(auction_id)) |
|||
is_subscribed = False |
|||
for subscriber in auction.bargain_subscribers_ids: |
|||
if subscriber.subscriber_id == request.env.user.partner_id: |
|||
is_subscribed = subscriber.is_subscribed |
|||
break |
|||
|
|||
return is_subscribed |
|||
|
|||
@http.route('/buy/now', type='json', auth='public', website=True, |
|||
csrf=False) |
|||
def buy_now(self, auction_id, product_id): |
|||
""" |
|||
Args: |
|||
auction_id: the ID of the auction obtained from the frontend |
|||
product_id: the ID of the product used to get data from the frontend |
|||
""" |
|||
# Find the auction with the given ID |
|||
auction = request.env['website.bargain'].sudo().browse(int(auction_id)) |
|||
# Create a draft sale order for the winner |
|||
sale_order = request.env['sale.order'].sudo().create({ |
|||
'partner_id': request.env.user.partner_id.id, |
|||
'state': 'draft', |
|||
}) |
|||
sale_order.sudo().write({ |
|||
'order_line': [(0, 0, { |
|||
'product_id': int(product_id), |
|||
'product_uom_qty': 1, |
|||
'name': '(auction won)' + auction.name, |
|||
'price_unit': auction.price_buy_now, |
|||
})] |
|||
}) |
|||
# Send a notification email to the winner |
|||
mail_compose_message = request.env['mail.compose.message'] |
|||
so_mcm_vals = sale_order.sudo().action_quotation_send().get('context', |
|||
{}) |
|||
compose_msg = mail_compose_message.sudo().with_context( |
|||
so_mcm_vals).create({}) |
|||
compose_msg.sudo().action_send_mail() |
@ -0,0 +1,21 @@ |
|||
<?xml version="1.0" encoding="UTF-8" ?> |
|||
<odoo> |
|||
<!-- Cron action for automatic email notifications --> |
|||
<record id="ir_cron_auto_send_email_admin" model="ir.cron"> |
|||
<field name="name">Auction: Email Notification</field> |
|||
<field name="model_id" ref="model_website_bargain"/> |
|||
<field name="state">code</field> |
|||
<field name="code">model.send_email_notification()</field> |
|||
<field name="interval_number">1</field> |
|||
<field name="interval_type">minutes</field> |
|||
</record> |
|||
<!-- Cron action for auction auto start --> |
|||
<record id="ir_cron_auto_start_auction" model="ir.cron"> |
|||
<field name="name">Auction: Auto Start</field> |
|||
<field name="model_id" ref="model_website_bargain"/> |
|||
<field name="state">code</field> |
|||
<field name="code">model.auction_auto_start()</field> |
|||
<field name="interval_number">1</field> |
|||
<field name="interval_type">minutes</field> |
|||
</record> |
|||
</odoo> |
@ -0,0 +1,104 @@ |
|||
<?xml version="1.0" encoding="UTF-8" ?> |
|||
<odoo> |
|||
<!-- Email template for admin notification --> |
|||
<record id="admin_email_template" model="mail.template"> |
|||
<field name="name">Auction: Admin Notification Email</field> |
|||
<field name="model_id" ref="website_bargain.model_website_bargain"/> |
|||
<field name="email_from">{{ |
|||
(object.auction_manager_id.partner_id.email) }} |
|||
</field> |
|||
<field name="email_to">{{ |
|||
(object.auction_manager_id.partner_id.email) }} |
|||
</field> |
|||
<field name="subject">{{ (object.name) }} end time is near</field> |
|||
<field name="body_html" type="html"> |
|||
<div style="margin: 0px; padding: 0px;"> |
|||
<p style="margin: 0px; padding: 0px; font-size: 13px"> |
|||
Dear |
|||
<t t-if="object.auction_manager_id.name"> |
|||
<t t-out="object.auction_manager_id.name"/> |
|||
</t> |
|||
<br/> |
|||
<br/> |
|||
This mail is a reminder that |
|||
<t t-if="object.name"> |
|||
<span style="font-weight:bold;" t-out="object.name"> |
|||
Auction |
|||
</span> |
|||
</t> |
|||
will end at |
|||
<t t-if="object.end_time"> |
|||
<span style="font-weight:bold;" |
|||
t-out="object.end_time">End Time |
|||
</span> |
|||
</t> |
|||
.So if its need to be |
|||
extended kindly reminding you to extend it fast. |
|||
</p> |
|||
</div> |
|||
</field> |
|||
</record> |
|||
<!-- Email template for auction start notification --> |
|||
<record id="email_template_auction_start" model="mail.template"> |
|||
<field name="name">Auction: Auction Start Notification Email</field> |
|||
<field name="model_id" ref="website_bargain.model_website_bargain"/> |
|||
<field name="email_from">{{ |
|||
(object.auction_manager_id.partner_id.email) }} |
|||
</field> |
|||
<field name="subject">{{ (object.name) }} has started</field> |
|||
<field name="body_html" type="html"> |
|||
<div style="margin: 0px; padding: 0px;"> |
|||
<p style="margin: 0px; padding: 0px; font-size: 13px"> |
|||
Dear Sir/Madam |
|||
<br/> |
|||
<br/> |
|||
This mail is a reminder that |
|||
<t t-if="object.name"> |
|||
<span style="font-weight:bold;" t-out="object.name"> |
|||
Auction |
|||
</span> |
|||
</t> |
|||
has started |
|||
<t t-if="object.start_time"> |
|||
<span style="font-weight:bold;" |
|||
t-out="object.end_time">Start Time Time |
|||
</span> |
|||
</t> |
|||
.Place your bids and win the auction. |
|||
</p> |
|||
</div> |
|||
</field> |
|||
</record> |
|||
<!-- Email template for auction extended notification --> |
|||
<record id="email_template_auction_extended" model="mail.template"> |
|||
<field name="name">Auction: Auction Extend Notification Email |
|||
</field> |
|||
<field name="model_id" ref="website_bargain.model_website_bargain"/> |
|||
<field name="email_from">{{ |
|||
(object.auction_manager_id.partner_id.email) }} |
|||
</field> |
|||
<field name="subject">{{ (object.name) }} has extended</field> |
|||
<field name="body_html" type="html"> |
|||
<div style="margin: 0px; padding: 0px;"> |
|||
<p style="margin: 0px; padding: 0px; font-size: 13px"> |
|||
Dear Sir/Madam |
|||
<br/> |
|||
<br/> |
|||
This mail is a reminder that |
|||
<t t-if="object.name"> |
|||
<span style="font-weight:bold;" t-out="object.name"> |
|||
Auction |
|||
</span> |
|||
</t> |
|||
has extended |
|||
<t t-if="object.extend_time"> |
|||
<span style="font-weight:bold;" |
|||
t-out="object.extend_time">Extended Time Time |
|||
</span> |
|||
</t> |
|||
.Place your bids and win the auction. |
|||
</p> |
|||
</div> |
|||
</field> |
|||
</record> |
|||
</odoo> |
@ -0,0 +1,7 @@ |
|||
## Module <website_bargain> |
|||
|
|||
#### 19.02.2025 |
|||
#### Version 18.0.1.0.0 |
|||
#### ADD |
|||
|
|||
- Initial Commit for Product Bidding In ECommerce |
@ -0,0 +1,26 @@ |
|||
# -*- coding: utf-8 -*- |
|||
############################################################################# |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
|||
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>) |
|||
# |
|||
# You can modify it under the terms of the GNU LESSER |
|||
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. |
|||
# |
|||
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE |
|||
# (LGPL v3) along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
############################################################################# |
|||
from . import bargain_information |
|||
from . import bargain_subscribers |
|||
from . import bargain_template |
|||
from . import product_template |
|||
from . import website_bargain |
@ -0,0 +1,44 @@ |
|||
# -*- coding: utf-8 -*- |
|||
############################################################################# |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
|||
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>) |
|||
# |
|||
# You can modify it under the terms of the GNU LESSER |
|||
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. |
|||
# |
|||
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE |
|||
# (LGPL v3) along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
############################################################################# |
|||
from odoo import fields, models |
|||
|
|||
|
|||
class BargainInformation(models.Model): |
|||
"""Class for adding bidder details to the database""" |
|||
_name = 'bargain.information' |
|||
_rec_name = 'auction_id' |
|||
_description = "Bidder Details" |
|||
_inherit = ['mail.thread', 'mail.activity.mixin'] |
|||
|
|||
bidder_id = fields.Many2one('res.partner', string='Bidder', required=True, |
|||
help="Bidders details") |
|||
auction_id = fields.Many2one('website.bargain', string='Auction', |
|||
required=True, help="Auction Details") |
|||
currency_id = fields.Many2one(related="auction_id.currency_id", |
|||
help="Currency Details") |
|||
bid_offer = fields.Monetary(string="Bid Offer", |
|||
currency_field='currency_id', |
|||
help="Offered amount") |
|||
status = fields.Char(string="Status", readonly=True, |
|||
help="Current Status of bid") |
|||
product_id = fields.Many2one(related='auction_id.product_id', |
|||
string='Product', help="Product details") |
@ -0,0 +1,45 @@ |
|||
# -*- coding: utf-8 -*- |
|||
############################################################################# |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
|||
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>) |
|||
# |
|||
# You can modify it under the terms of the GNU LESSER |
|||
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. |
|||
# |
|||
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE |
|||
# (LGPL v3) along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
############################################################################# |
|||
from odoo import fields, models |
|||
|
|||
|
|||
class BargainSubscribers(models.Model): |
|||
"""Model for adding the subscriber details """ |
|||
_name = 'bargain.subscribers' |
|||
_inherit = ['mail.thread', 'mail.activity.mixin'] |
|||
_description = "Subscriber details" |
|||
|
|||
subscriber_id = fields.Many2one('res.partner', required=True, |
|||
help="Subscribers id will be here ") |
|||
name = fields.Char(related='subscriber_id.name', string='Name', |
|||
readonly=False, help="Subscribers name", |
|||
required=True) |
|||
email = fields.Char(related='subscriber_id.email', string='Email', |
|||
readonly=False, help="Subscribers email") |
|||
auction_id = fields.Many2one('website.bargain', string='Auction', |
|||
required=True, help="Auction details") |
|||
subscribe_time = fields.Datetime(string='Subscribe On', |
|||
default=fields.Datetime.today(), |
|||
readonly=True, |
|||
help="Time of subscription") |
|||
is_subscribed = fields.Boolean(string="Is Subscribed", |
|||
help="Check if Subscribed or not") |
@ -0,0 +1,36 @@ |
|||
# -*- coding: utf-8 -*- |
|||
############################################################################# |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
|||
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>) |
|||
# |
|||
# You can modify it under the terms of the GNU LESSER |
|||
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. |
|||
# |
|||
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE |
|||
# (LGPL v3) along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
############################################################################# |
|||
from odoo import fields, models |
|||
|
|||
|
|||
class BargainTemplate(models.Model): |
|||
"""Model for auction template""" |
|||
|
|||
_name = 'bargain.template' |
|||
_inherit = ['mail.thread', 'mail.activity.mixin'] |
|||
_description = "Bargain Template" |
|||
|
|||
name = fields.Char(string='Template Name', help="Name of the template") |
|||
product_id = fields.Many2one('product.template', |
|||
domain=[('is_published', '=', True)], |
|||
required=True, |
|||
help="Choose the product from the list") |
@ -0,0 +1,31 @@ |
|||
# -*- coding: utf-8 -*- |
|||
############################################################################# |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
|||
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>) |
|||
# |
|||
# You can modify it under the terms of the GNU LESSER |
|||
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. |
|||
# |
|||
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE |
|||
# (LGPL v3) along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
############################################################################# |
|||
from odoo import fields, models |
|||
|
|||
|
|||
class ProductTemplateInherit(models.Model): |
|||
"""Model to add a new boolean field to product template""" |
|||
_inherit = 'product.template' |
|||
|
|||
is_auction = fields.Boolean(string='Auction On', readonly=True, |
|||
help="If auction is on this field will be true" |
|||
) |
@ -0,0 +1,301 @@ |
|||
# -*- coding: utf-8 -*- |
|||
############################################################################# |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
|||
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>) |
|||
# |
|||
# You can modify it under the terms of the GNU LESSER |
|||
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. |
|||
# |
|||
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE |
|||
# (LGPL v3) along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
############################################################################# |
|||
from datetime import timedelta |
|||
from odoo import api, fields, models, _ |
|||
from odoo.exceptions import ValidationError |
|||
|
|||
|
|||
class WebsiteBargain(models.Model): |
|||
"""This is the main class of website bargain where all the details are |
|||
stored in this model |
|||
_onchange_template_id: |
|||
function for automatically add the auction name |
|||
_check_end_date: |
|||
function to validate the end date if it is higher than the start |
|||
date |
|||
_check_extend_time: |
|||
function to validate extended time weather it is greater than end |
|||
time and after saving will send notification to subscribers if |
|||
enabled |
|||
action_confirm: |
|||
button function to confirm auction and will make product as |
|||
published |
|||
action_reset_to_draft: |
|||
button function to reset the auction to draft |
|||
action_run_auction: |
|||
button function to start auction manually |
|||
action_complete: |
|||
button function to end auction manually |
|||
action_close: |
|||
button function to close auction |
|||
send_email_notification: |
|||
Function for sending auction notification for admin |
|||
before ending if enabled |
|||
auction_auto_start: |
|||
Cron function for auction auto start when its start |
|||
time and also will send notification to customers about auction |
|||
""" |
|||
_name = 'website.bargain' |
|||
_description = "Website Bargain" |
|||
_inherit = ['mail.thread', 'mail.activity.mixin'] |
|||
|
|||
website_id = fields.Many2one('website', string='Website', required=True, |
|||
help="Add website here to put auction") |
|||
template_id = fields.Many2one('bargain.template', string='Template', |
|||
help="add already created templates of " |
|||
"auction with products") |
|||
auction_manager_id = fields.Many2one('res.users', string="Auction Manager", |
|||
help="Set suction manager here", |
|||
required=True) |
|||
product_id = fields.Many2one(related='template_id.product_id', store=True, |
|||
string='Product', readonly=False, |
|||
help="Select the product", required=True) |
|||
name = fields.Char(string='Auction Name', help="Auctions name") |
|||
currency_id = fields.Many2one('res.currency', 'Currency', |
|||
default=lambda |
|||
self: self.env.user.company_id.currency_id.id, |
|||
required=True, |
|||
help="if you select this " |
|||
"currency bidding will " |
|||
"be on that currency itself") |
|||
initial_price = fields.Monetary(string='Initial Price', |
|||
currency_field='currency_id', |
|||
required=True, |
|||
help="Minimum amount required to bid") |
|||
is_buy_now = fields.Boolean(string='Provide buy now button', |
|||
help='Enable a button for buying instantly') |
|||
price_buy_now = fields.Monetary(string='Buy Now Price', |
|||
currency_field='currency_id', |
|||
help="Price of the product if its" |
|||
" immediately buying") |
|||
start_time = fields.Datetime(string='Start Date Time', required=True, |
|||
default=fields.Datetime.today(), |
|||
help="Auction start time") |
|||
end_time = fields.Datetime(string='End Date Time', required=True, |
|||
help="Auction start time") |
|||
extend_time = fields.Datetime(string="Extended Time", |
|||
help="By setting days you can " |
|||
"extend the auction") |
|||
state = fields.Selection( |
|||
[('draft', "Draft"), ('confirmed', "Confirmed"), |
|||
('running', "Running"), |
|||
('closed', "Closed"), ('finished', "Finished")], default='draft') |
|||
bargain_information_ids = fields.One2many('bargain.information', |
|||
'auction_id', readonly=True) |
|||
bargain_subscribers_ids = fields.One2many('bargain.subscribers', |
|||
'auction_id') |
|||
is_activate = fields.Boolean(string='Activate', |
|||
help='this will activate ' |
|||
'notification for the admins') |
|||
notify_on = fields.Integer(string='Notify before', default='5', |
|||
help="Set notification time here") |
|||
notify_selection = fields.Selection( |
|||
[('days', "Days"), ('hours', "Hours"), ('minutes', "Minutes"), |
|||
('seconds', "Seconds")], default='minutes') |
|||
is_notification_send = fields.Boolean(string='Notification Send', |
|||
help='If notification send manually ' |
|||
'you can enable this field ' |
|||
'to restrict ' |
|||
'sending notification before ' |
|||
'the time added') |
|||
is_winner_notification = fields.Boolean( |
|||
string='Send Notification to Winner', |
|||
help='Enable this option to send notification to the winner') |
|||
is_loser_notification = fields.Boolean(string='Send Notification to Loser', |
|||
help='Enable this option to send ' |
|||
'notification to the losers') |
|||
is_subscriber_start_notification = fields.Boolean(string='Bidding Start', |
|||
help="Send subscribers " |
|||
"the bidding start " |
|||
"notification") |
|||
is_send_mail = fields.Boolean(string='Bidding Started Mail Sent', |
|||
help='If Bidding mail has been sent to customer') |
|||
is_extend_auction_notification = fields.Boolean(string='Auction Extended', |
|||
help="Send subscribers the" |
|||
"bidding extended " |
|||
"notification") |
|||
is_new_bid_notification = fields.Boolean(string='New Bid Placed', |
|||
help="Send subscribers " |
|||
"notifications if new bids" |
|||
" are placed") |
|||
is_bid_end_notification = fields.Boolean(string='Auction Finished', |
|||
help="Send subscribers " |
|||
"notifications if bidding " |
|||
"is over") |
|||
product_img = fields.Binary(related="product_id.image_1920") |
|||
product_description = fields.Text(related="product_id.description_sale", |
|||
string="Auction Product Description") |
|||
|
|||
@api.onchange('template_id', 'product_id') |
|||
def _onchange_template_id(self): |
|||
"""summary: |
|||
function to add name automatically""" |
|||
if self.product_id: |
|||
self.name = f"Auction for {self.product_id.name}" |
|||
|
|||
@api.constrains('end_time', 'start_time') |
|||
def _check_end_date(self): |
|||
""" |
|||
Summary: |
|||
start and end date validation function |
|||
""" |
|||
if self.end_time <= self.start_time: |
|||
raise ValidationError( |
|||
_('End time should be greater than start time')) |
|||
|
|||
@api.constrains('extend_time') |
|||
def _check_extend_time(self): |
|||
"""Summary: |
|||
function to validate extended time weather it is greater than |
|||
end time and after saving will send notification to subscribers |
|||
if enabled""" |
|||
if self.extend_time: |
|||
if self.extend_time <= self.end_time: |
|||
raise ValidationError( |
|||
_('This time is not greater than the old time(' + str( |
|||
self.end_time) + ')')) |
|||
if self.is_extend_auction_notification: |
|||
template_id = self.env.ref( |
|||
'website_bargain.email_template_auction_extended') |
|||
email_to = '' |
|||
for subscriber in self.bargain_subscribers_ids: |
|||
email_to += subscriber.email + ',' |
|||
template_id.email_to = email_to |
|||
template_id.send_mail(self.id, force_send=True) |
|||
|
|||
def action_confirm(self): |
|||
""" |
|||
Summary: |
|||
button function to confirm auction and will |
|||
make product as published |
|||
""" |
|||
if self.search([('product_id', '=', self.product_id.id), |
|||
('state', '=', 'running')]): |
|||
raise ValidationError( |
|||
_('Already an auction is running for this product please' |
|||
' close it to continue')) |
|||
if self.end_time <= fields.Datetime.today(): |
|||
raise ValidationError(_('End time is already over')) |
|||
self.product_id.is_published = True |
|||
self.product_id.website_id = self.website_id |
|||
self.write({'state': 'confirmed'}) |
|||
|
|||
def action_reset_to_draft(self): |
|||
""" |
|||
Summary: |
|||
button function to reset the auction to draft |
|||
""" |
|||
self.product_id.is_auction = False |
|||
self.write({'state': 'draft'}) |
|||
|
|||
def action_run_auction(self): |
|||
""" |
|||
Summary: |
|||
button function to start auction manually |
|||
""" |
|||
if self.search([('product_id', '=', self.product_id.id), |
|||
('state', '=', 'running')]): |
|||
raise ValidationError( |
|||
_('Already an auction is running for this product ' |
|||
'please close it to continue')) |
|||
self.product_id.is_auction = True |
|||
self.write({'state': 'running'}) |
|||
|
|||
def action_complete(self): |
|||
""" |
|||
Summary: |
|||
button function to end auction manually |
|||
""" |
|||
self.product_id.is_auction = False |
|||
self.product_id.is_published = False |
|||
self.write({'state': 'finished'}) |
|||
|
|||
def action_close(self): |
|||
""" |
|||
Summary: |
|||
button function to close auction |
|||
""" |
|||
self.product_id.is_auction = False |
|||
self.product_id.is_published = False |
|||
self.write({'state': 'closed'}) |
|||
|
|||
def send_email_notification(self): |
|||
""" |
|||
Summary: |
|||
Function for sending auction notification for admin e ending if |
|||
enabled |
|||
""" |
|||
# Get the email template for the admin notification |
|||
template_id = self.env.ref( |
|||
'website_bargain.admin_email_template') |
|||
# Find all running auctions that have notification enabled and have |
|||
# not been sent yet |
|||
auctions = self.search([('state', '=', 'running'), |
|||
('is_activate', '=', True), |
|||
('notify_on', '>', 0), |
|||
('is_notification_send', '=', False)]) |
|||
# Loop through the auctions that need to be notified |
|||
for auction in auctions: |
|||
# Map the time unit to a string for timedelta |
|||
time_unit_map = {'days': 'days', 'hours': 'hours', |
|||
'minutes': 'minutes', 'seconds': 'seconds'} |
|||
time_unit = time_unit_map.get(auction.notify_selection) |
|||
# Check if it's time to send the notification |
|||
if time_unit: |
|||
next_notification = auction.end_time - timedelta( |
|||
**{time_unit: auction.notify_on}) |
|||
if next_notification <= fields.Datetime.today(): |
|||
# Send the notification email and set the flag to True |
|||
template_id.send_mail(auction.id, force_send=True) |
|||
auction.is_notification_send = True |
|||
|
|||
def auction_auto_start(self): |
|||
""" |
|||
Summary: |
|||
Cron function for auction auto start when its start |
|||
time and also will send notification to customers about auction |
|||
""" |
|||
# Get all confirmed auctions |
|||
auctions = self.search([('state', '=', 'confirmed')]) |
|||
# Get the email template for auction start notification |
|||
template_id = self.env.ref( |
|||
'website_bargain.email_template_auction_start') |
|||
# Get the emails of all the bidders and sale order partners |
|||
bidders = self.env['bargain.information'].search([]).mapped( |
|||
'bidder_id.email') |
|||
sale_orders_partner = self.env['sale.order'].search( |
|||
[('state', '=', 'sale')]).mapped('partner_id.email') |
|||
# Loop through the auctions and check if any auction's start time has arrived |
|||
for auction in auctions: |
|||
if auction.start_time <= fields.Datetime.today(): |
|||
# Set the product as an auction and update the website |
|||
auction.product_id.is_auction = True |
|||
auction.product.website_id = auction.website_id |
|||
auction.write({'state': 'running'}) |
|||
# Send email notifications to all the bidders and sale order partners |
|||
email_to = "" |
|||
for partner in sale_orders_partner: |
|||
email_to += partner + ',' |
|||
for bidder in bidders: |
|||
email_to += bidder + ',' |
|||
template_id.email_to = email_to |
|||
template_id.send_mail(auction.id, force_send=True) |
|
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 628 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 210 KiB |
After Width: | Height: | Size: 209 KiB |
After Width: | Height: | Size: 109 KiB |
After Width: | Height: | Size: 495 B |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 624 B |
After Width: | Height: | Size: 136 KiB |
After Width: | Height: | Size: 214 KiB |
After Width: | Height: | Size: 36 KiB |
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 310 B |
After Width: | Height: | Size: 929 B |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 542 B |
After Width: | Height: | Size: 576 B |
After Width: | Height: | Size: 733 B |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 4.0 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 738 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 911 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 600 B |
After Width: | Height: | Size: 673 B |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 462 B |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 926 B |
After Width: | Height: | Size: 9.0 KiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 7.0 KiB |
After Width: | Height: | Size: 878 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 653 B |
After Width: | Height: | Size: 800 B |
After Width: | Height: | Size: 905 B |
After Width: | Height: | Size: 189 KiB |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 839 B |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 5.9 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 427 B |
After Width: | Height: | Size: 627 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 988 B |
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 5.0 KiB |
After Width: | Height: | Size: 875 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 767 KiB |
After Width: | Height: | Size: 138 KiB |
After Width: | Height: | Size: 760 KiB |
After Width: | Height: | Size: 92 KiB |
After Width: | Height: | Size: 697 KiB |
After Width: | Height: | Size: 1.1 MiB |
After Width: | Height: | Size: 93 KiB |
After Width: | Height: | Size: 162 KiB |
After Width: | Height: | Size: 152 KiB |
After Width: | Height: | Size: 163 KiB |
After Width: | Height: | Size: 384 KiB |
After Width: | Height: | Size: 337 KiB |
After Width: | Height: | Size: 172 KiB |
After Width: | Height: | Size: 345 KiB |
After Width: | Height: | Size: 188 KiB |
After Width: | Height: | Size: 176 KiB |
After Width: | Height: | Size: 605 KiB |