Browse Source

Oct 16 [UPDT] : Updated 'base_accounting_kit'

18.0
AjmalCybro 3 days ago
parent
commit
aaf2516861
  1. 1
      base_accounting_kit/__init__.py
  2. 6
      base_accounting_kit/__manifest__.py
  3. 22
      base_accounting_kit/controllers/__init__.py
  4. 54
      base_accounting_kit/controllers/statement_report.py
  5. 5
      base_accounting_kit/doc/RELEASE_NOTES.md
  6. 370
      base_accounting_kit/models/res_partner.py
  7. 14
      base_accounting_kit/report/res_partner_reports.xml
  8. 76
      base_accounting_kit/report/res_partner_templates.xml
  9. BIN
      base_accounting_kit/static/description/assets/screenshots/customer_statement.png
  10. BIN
      base_accounting_kit/static/description/assets/screenshots/customer_statement_excel.png
  11. BIN
      base_accounting_kit/static/description/assets/screenshots/customer_statement_pdf.png
  12. 185
      base_accounting_kit/static/description/index.html
  13. 16
      base_accounting_kit/static/src/js/action_manager.js
  14. 93
      base_accounting_kit/views/res_partner_views.xml

1
base_accounting_kit/__init__.py

@ -19,6 +19,7 @@
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
from . import controllers
from . import models
from . import report
from . import wizard

6
base_accounting_kit/__manifest__.py

@ -21,7 +21,7 @@
#############################################################################
{
'name': 'Odoo 18 Full Accounting Kit for Community',
'version': '18.0.4.0.6',
'version': '18.0.5.0.6',
'category': 'Accounting',
'live_test_url': 'https://kit.easyinstance.com/web/login?redirect=/odoo/accounting',
'summary': """Odoo 18 Accounting, Odoo 18 Accounting Reports, Odoo18 Accounting, Odoo Accounting, Odoo18 Financial Reports, Odoo18 Asset, Odoo18 Profit and Loss, PDC, Followups, Odoo18, Accounting, Odoo Apps, Reports""",
@ -53,6 +53,7 @@
'views/account_followup.xml',
'views/followup_line_views.xml',
'views/followup_report.xml',
'views/res_partner_views.xml',
'wizard/asset_depreciation_confirmation_views.xml',
'wizard/asset_modify_views.xml',
'views/account_asset_asset_views.xml',
@ -88,6 +89,8 @@
'report/report.xml',
'report/multiple_invoice_layouts.xml',
'report/multiple_invoice_report_template.xml',
'report/res_partner_reports.xml',
'report/res_partner_templates.xml',
'views/account_recurring_payments_view.xml',
'views/account_move_line_views.xml',
'views/account_bank_statement_views.xml',
@ -107,6 +110,7 @@
'base_accounting_kit/static/src/js/KanbanController.js',
'base_accounting_kit/static/src/js/ListController.js',
'base_accounting_kit/static/src/js/bank_reconcile_form_lines_widget.js',
'base_accounting_kit/static/src/js/action_manager.js',
'base_accounting_kit/static/src/xml/bank_rec_widget.xml',
'base_accounting_kit/static/src/xml/bank_reconcile_widget.xml',
]

22
base_accounting_kit/controllers/__init__.py

@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
###############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Aysha Shalin (odoo@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 statement_report

54
base_accounting_kit/controllers/statement_report.py

@ -0,0 +1,54 @@
# -*- coding: utf-8 -*-
###############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Aysha Shalin (odoo@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/>.
#
###############################################################################
import json
from odoo import http
from odoo.http import content_disposition, request
from odoo.tools import html_escape
class XLSXReportController(http.Controller):
""" Controller for xlsx report """
@http.route('/xlsx_report', type='http', auth='user', methods=['POST'],
csrf=False)
def get_report_xlsx(self, model, options, output_format, report_name):
""" Get xlsx report data """
report_obj = request.env[model].sudo()
print("oo")
options = json.loads(options)
try:
if output_format == 'xlsx':
response = request.make_response(
None, headers=[
('Content-Type', 'application/vnd.ms-excel'),
('Content-Disposition', content_disposition(
report_name + '.xlsx'))])
report_obj.get_xlsx_report(options, response)
response.set_cookie('fileToken', 'dummy token')
return response
except Exception as event:
serialize = http.serialize_exception(event)
error = {
'code': 200,
'message': 'Odoo Server Error',
'data': serialize
}
return request.make_response(html_escape(json.dumps(error)))

5
base_accounting_kit/doc/RELEASE_NOTES.md

@ -44,3 +44,8 @@
#### Version 18.0.4.0.6
#### FIX
- Updated the generation of asset entry when the compute depreciation button click.
#### 13.10.2025
#### Version 18.0.5.0.6
#### UPDT
- Added Customer Statement feature.

370
base_accounting_kit/models/res_partner.py

@ -21,8 +21,12 @@
#############################################################################
from datetime import date, timedelta
from odoo import api, fields, models, _
from odoo.exceptions import UserError
import base64
import io
import json
import xlsxwriter
from odoo.exceptions import ValidationError, UserError
from odoo.tools.json import json_default
class ResPartner(models.Model):
"""Inheriting res.partner"""
@ -64,6 +68,22 @@ class ResPartner(models.Model):
enable_credit_limit = fields.Boolean(string="Credit Limit Enabled",
compute="_compute_enable_credit_limit")
# customer statement
customer_report_ids = fields.Many2many(
'account.move',
compute='_compute_customer_report_ids',
help='Partner Invoices related to Customer')
vendor_statement_ids = fields.Many2many(
'account.move',
compute='_compute_vendor_statement_ids',
help='Partner Bills related to Vendor')
currency_id = fields.Many2one(
'res.currency',
default=lambda self: self.env.company.currency_id.id,
help="currency related to Customer or Vendor")
def _compute_for_followup(self):
"""
Compute the fields 'total_due', 'total_overdue' , 'next_reminder_date' and 'followup_status'
@ -159,3 +179,349 @@ class ResPartner(models.Model):
if self.blocking_stage > 0:
raise UserError(_(
"Warning amount should be less than Blocking amount"))
# customer statement
def _compute_customer_report_ids(self):
""" For computing 'invoices' of partner """
for rec in self:
inv_ids = self.env['account.move'].search(
[('partner_id', '=', rec.id),
('move_type', '=', 'out_invoice'),
('payment_state', '!=', 'paid'),
('state', '=', 'posted')])
rec.customer_report_ids = inv_ids
def _compute_vendor_statement_ids(self):
""" For computing 'bills' of partner """
for rec in self:
bills = self.env['account.move'].search(
[('partner_id', '=', rec.id),
('move_type', '=', 'in_invoice'),
('payment_state', '!=', 'paid'),
('state', '=', 'posted')])
rec.vendor_statement_ids = bills
def main_query(self):
""" Return select query """
query = """SELECT name , invoice_date, invoice_date_due,
amount_total_signed AS sub_total,
amount_residual_signed AS amount_due ,
amount_residual AS balance
FROM account_move WHERE payment_state != 'paid'
AND state ='posted' AND partner_id= '%s'
AND company_id = '%s' """ % (self.id, self.env.company.id)
return query
def amount_query(self):
""" Return query for calculating total amount """
amount_query = """ SELECT SUM(amount_total_signed) AS total,
SUM(amount_residual) AS balance
FROM account_move WHERE payment_state != 'paid'
AND state ='posted' AND partner_id= '%s'
AND company_id = '%s' """ % (self.id, self.env.company.id)
return amount_query
def action_share_pdf(self):
""" Action for sharing customer pdf report """
if self.customer_report_ids:
main_query = self.main_query()
main_query += """ AND move_type IN ('out_invoice')"""
amount = self.amount_query()
amount += """ AND move_type IN ('out_invoice')"""
self.env.cr.execute(main_query)
main = self.env.cr.dictfetchall()
self.env.cr.execute(amount)
amount = self.env.cr.dictfetchall()
data = {
'customer': self.display_name,
'street': self.street,
'street2': self.street2,
'city': self.city,
'state': self.state_id.name,
'zip': self.zip,
'my_data': main,
'total': amount[0]['total'],
'balance': amount[0]['balance'],
'currency': self.currency_id.symbol,
}
report = self.env['ir.actions.report'].sudo()._render_qweb_pdf(
'base_accounting_kit.res_partner_action', self, data=data)
data_record = base64.b64encode(report[0])
ir_values = {
'name': 'Statement Report',
'type': 'binary',
'datas': data_record,
'mimetype': 'application/pdf',
'res_model': 'res.partner'
}
attachment = self.env['ir.attachment'].sudo().create(ir_values)
email_values = {
'email_to': self.email,
'subject': 'Payment Statement Report',
'body_html': '<p>Dear <strong> Mr/Miss. ' + self.name +
'</strong> </p> <p> We have attached your '
'payment statement. Please check </p> '
'<p>Best regards, </p> <p> ' + self.env.user.name,
'attachment_ids': [attachment.id],
}
mail = self.env['mail.mail'].sudo().create(email_values)
mail.send()
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'message': 'Email Sent Successfully',
'type': 'success',
'sticky': False
}
}
else:
raise ValidationError('There is no statement to send')
def action_print_pdf(self):
""" Action for printing pdf report """
if self.customer_report_ids:
main_query = self.main_query()
main_query += """ AND move_type IN ('out_invoice')"""
amount = self.amount_query()
amount += """ AND move_type IN ('out_invoice')"""
self.env.cr.execute(main_query)
main = self.env.cr.dictfetchall()
self.env.cr.execute(amount)
amount = self.env.cr.dictfetchall()
data = {
'customer': self.display_name,
'street': self.street,
'street2': self.street2,
'city': self.city,
'state': self.state_id.name,
'zip': self.zip,
'my_data': main,
'total': amount[0]['total'],
'balance': amount[0]['balance'],
'currency': self.currency_id.symbol,
}
return self.env.ref('base_accounting_kit.res_partner_action'
).report_action(self, data=data)
else:
raise ValidationError('There is no statement to print')
def action_print_xlsx(self):
""" Action for printing xlsx report of customers """
if self.customer_report_ids:
main_query = self.main_query()
main_query += """ AND move_type IN ('out_invoice')"""
amount = self.amount_query()
amount += """ AND move_type IN ('out_invoice')"""
self.env.cr.execute(main_query)
main = self.env.cr.dictfetchall()
self.env.cr.execute(amount)
amount = self.env.cr.dictfetchall()
data = {
'customer': self.display_name,
'street': self.street,
'street2': self.street2,
'city': self.city,
'state': self.state_id.name,
'zip': self.zip,
'my_data': main,
'total': amount[0]['total'],
'balance': amount[0]['balance'],
'currency': self.currency_id.symbol,
}
return {
'type': 'ir.actions.report',
'data': {
'model': 'res.partner',
'options': json.dumps(data,
default=json_default),
'output_format': 'xlsx',
'report_name': 'Payment Statement Report'
},
'report_type': 'xlsx',
}
else:
raise ValidationError('There is no statement to print')
def get_xlsx_report(self, data, response):
""" Get xlsx report data """
output = io.BytesIO()
workbook = xlsxwriter.Workbook(output, {'in_memory': True})
sheet = workbook.add_worksheet()
cell_format_with_color = workbook.add_format({
'font_size': '14px', 'bold': True,
'bg_color': 'yellow', 'border': 1})
cell_format = workbook.add_format({'font_size': '14px', 'bold': True})
txt = workbook.add_format({'font_size': '13px'})
txt_border = workbook.add_format({'font_size': '13px', 'border': 1})
head = workbook.add_format({'align': 'center', 'bold': True,
'font_size': '22px'})
sheet.merge_range('B2:Q4', 'Payment Statement Report', head)
if data['customer']:
sheet.merge_range('B7:D7', 'Customer/Supplier : ', cell_format)
sheet.merge_range('E7:H7', data['customer'], txt)
sheet.merge_range('B9:C9', 'Address : ', cell_format)
if data['street']:
sheet.merge_range('D9:F9', data['street'], txt)
if data['street2']:
sheet.merge_range('D10:F10', data['street2'], txt)
if data['city']:
sheet.merge_range('D11:F11', data['city'], txt)
if data['state']:
sheet.merge_range('D12:F12', data['state'], )
if data['zip']:
sheet.merge_range('D13:F13', data['zip'], txt)
sheet.merge_range('B15:C15', 'Date', cell_format_with_color)
sheet.merge_range('D15:G15', 'Invoice/Bill Number',
cell_format_with_color)
sheet.merge_range('H15:I15', 'Due Date', cell_format_with_color)
sheet.merge_range('J15:L15', 'Invoices/Debit', cell_format_with_color)
sheet.merge_range('M15:O15', 'Amount Due', cell_format_with_color)
sheet.merge_range('P15:R15', 'Balance Due', cell_format_with_color)
row = 15
column = 0
for record in data['my_data']:
sub_total = data['currency'] + str(record['sub_total'])
amount_due = data['currency'] + str(record['amount_due'])
balance = data['currency'] + str(record['balance'])
total = data['currency'] + str(data['total'])
remain_balance = data['currency'] + str(data['balance'])
sheet.merge_range(row, column + 1, row, column + 2,
record['invoice_date'], txt_border)
sheet.merge_range(row, column + 3, row, column + 6,
record['name'], txt_border)
sheet.merge_range(row, column + 7, row, column + 8,
record['invoice_date_due'], txt_border)
sheet.merge_range(row, column + 9, row, column + 11,
sub_total, txt_border)
sheet.merge_range(row, column + 12, row, column + 14,
amount_due, txt_border)
sheet.merge_range(row, column + 15, row, column + 17,
balance, txt_border)
row = row + 1
sheet.write(row + 2, column + 1, 'Total Amount: ', cell_format)
sheet.merge_range(row + 2, column + 3, row + 2, column + 4,
total, txt)
sheet.write(row + 4, column + 1, 'Balance Due: ', cell_format)
sheet.merge_range(row + 4, column + 3, row + 4, column + 4,
remain_balance, txt)
workbook.close()
output.seek(0)
response.stream.write(output.read())
output.close()
def action_share_xlsx(self):
""" Action for sharing xlsx report via email """
if self.customer_report_ids:
main_query = self.main_query()
main_query += """ AND move_type IN ('out_invoice')"""
amount = self.amount_query()
amount += """ AND move_type IN ('out_invoice')"""
self.env.cr.execute(main_query)
main = self.env.cr.dictfetchall()
self.env.cr.execute(amount)
amount = self.env.cr.dictfetchall()
data = {
'customer': self.display_name,
'street': self.street,
'street2': self.street2,
'city': self.city,
'state': self.state_id.name,
'zip': self.zip,
'my_data': main,
'total': amount[0]['total'],
'balance': amount[0]['balance'],
'currency': self.currency_id.symbol,
}
output = io.BytesIO()
workbook = xlsxwriter.Workbook(output, {'in_memory': True})
sheet = workbook.add_worksheet()
cell_format = workbook.add_format({
'font_size': '14px', 'bold': True})
txt = workbook.add_format({'font_size': '13px'})
head = workbook.add_format(
{'align': 'center', 'bold': True, 'font_size': '22px'})
sheet.merge_range('B2:P4', 'Payment Statement Report', head)
date_style = workbook.add_format(
{'text_wrap': True, 'align': 'center',
'num_format': 'yyyy-mm-dd'})
if data['customer']:
sheet.write('B7:C7', 'Customer : ', cell_format)
sheet.merge_range('D7:G7', data['customer'], txt)
sheet.write('B9:C7', 'Address : ', cell_format)
if data['street']:
sheet.merge_range('D9:F9', data['street'], txt)
if data['street2']:
sheet.merge_range('D10:F10', data['street2'], txt)
if data['city']:
sheet.merge_range('D11:F11', data['city'], txt)
if data['state']:
sheet.merge_range('D12:F12', data['state'], txt)
if data['zip']:
sheet.merge_range('D13:F13', data['zip'], txt)
sheet.write('B15', 'Date', cell_format)
sheet.write('D15', 'Invoice/Bill Number', cell_format)
sheet.write('H15', 'Due Date', cell_format)
sheet.write('J15', 'Invoices/Debit', cell_format)
sheet.write('M15', 'Amount Due', cell_format)
sheet.write('P15', 'Balance Due', cell_format)
row = 16
column = 0
for record in data['my_data']:
sub_total = data['currency'] + str(record['sub_total'])
amount_due = data['currency'] + str(record['amount_due'])
balance = data['currency'] + str(record['balance'])
total = data['currency'] + str(data['total'])
remain_balance = data['currency'] + str(data['balance'])
sheet.merge_range(row, column + 1, row, column + 2,
record['invoice_date'], date_style)
sheet.merge_range(row, column + 3, row, column + 5,
record['name'], txt)
sheet.merge_range(row, column + 7, row, column + 8,
record['invoice_date_due'], date_style)
sheet.merge_range(row, column + 9, row, column + 10,
sub_total, txt)
sheet.merge_range(row, column + 12, row, column + 13,
amount_due, txt)
sheet.merge_range(row, column + 15, row, column + 16,
balance, txt)
row = row + 1
sheet.write(row + 2, column + 1, 'Total Amount : ', cell_format)
sheet.merge_range(row + 2, column + 4, row + 2, column + 5,
total, txt)
sheet.write(row + 4, column + 1, 'Balance Due : ', cell_format)
sheet.merge_range(row + 4, column + 4, row + 4, column + 5,
remain_balance, txt)
workbook.close()
output.seek(0)
xlsx = base64.b64encode(output.read())
output.close()
ir_values = {
'name': "Statement Report.xlsx",
'type': 'binary',
'datas': xlsx,
'store_fname': xlsx,
}
attachment = self.env['ir.attachment'].sudo().create(ir_values)
email_values = {
'email_to': self.email,
'subject': 'Payment Statement Report',
'body_html': '<p>Dear <strong> Mr/Miss. ' + self.name +
'</strong> </p> <p> We have attached your'
' payment statement. Please check </p> '
'<p>Best regards, </p> <p> ' + self.env.user.name,
'attachment_ids': [attachment.id],
}
mail = self.env['mail.mail'].sudo().create(email_values)
mail.send()
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'message': 'Email Sent Successfully',
'type': 'success',
'sticky': False
}
}
else:
raise ValidationError('There is no statement to send')

14
base_accounting_kit/report/res_partner_reports.xml

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<!-- Action for statement report -->
<record id="res_partner_action" model="ir.actions.report">
<field name="name">Statement Report</field>
<field name="model">res.partner</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">base_accounting_kit.res_partner_statement_report_template</field>
<field name="report_file">base_accounting_kit.res_partner_statement_report_template</field>
<field name="print_report_name">'Statement Report- %s' %(object.name)</field>
<field name="binding_model_id" ref="model_res_partner"/>
<field name="binding_type">report</field>
</record>
</odoo>

76
base_accounting_kit/report/res_partner_templates.xml

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<!-- Statement report template -->
<template id="res_partner_statement_report_template">
<t t-call="web.html_container">
<t t-call="web.external_layout">
<div page="page">
<h3>Payment Statement Report</h3>
</div><br/>
<table border="0">
<tr><t t-esc="customer"/></tr><br/><br/>
<tr><t t-if="street"> <t t-esc="street"/></t></tr><br/>
<tr><t t-if="street2"> <t t-esc="street2"/></t></tr><br/>
<tr><t t-if="city"> <t t-esc="city"/></t></tr><br/>
<tr><t t-if="state"> <t t-esc="state"/></t></tr><br/>
</table>
<br/><br/>
<table class="table" style="align-items: center;">
<thead>
<tr>
<th>Date</th>
<th>Invoice/Bill Number</th>
<th>Due Date</th>
<th>Invoices/Debit</th>
<th>Balance</th>
</tr>
</thead>
<tbody>
<t t-foreach="my_data" t-as="line">
<tr>
<td align="center"><t t-esc="line['invoice_date']"/></td>
<td align="center"><t t-esc="line['name']"/></td>
<td align="center"><t t-esc="line['invoice_date_due']"/></td>
<td align="center">
<t t-esc="currency"/>
<t t-esc="line['sub_total']"/>
</td>
<td align="center">
<t t-esc="currency"/>
<t t-esc="line['balance']"/>
</td>
</tr>
</t>
</tbody>
</table>
<br/>
<t t-if="total">
<div class="clearfix" name="so_total_summary">
<div id="total" class="row" name="total">
<div t-attf-class="#{'col-6' if report_type != 'html' else 'col-sm-7 col-md-6'} ms-auto">
<table class="table table-sm">
<tbody>
<tr>
<td>Total Amount:</td>
<td>
<t t-esc="currency"/>
<t t-esc="total"/>
</td>
</tr>
<tr>
<td>Total Balance:</td>
<td>
<t t-esc="currency"/>
<t t-esc="balance"/>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</t>
</t>
</t>
</template>
</odoo>

BIN
base_accounting_kit/static/description/assets/screenshots/customer_statement.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

BIN
base_accounting_kit/static/description/assets/screenshots/customer_statement_excel.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

BIN
base_accounting_kit/static/description/assets/screenshots/customer_statement_pdf.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

185
base_accounting_kit/static/description/index.html

@ -287,6 +287,66 @@
</div>
</div>
<!-- Requirements -->
<section class="oe_container">
<div class="mt64 mb64">
<div class="col-sm-12 py-4">
<div class="alert alert-primary mt-4"
style="color: #000;background-color: #f8f8f8;border: 1px solid #dcdcdc;border-radius: 16px;">
This module uses some external python dependencies :
openpyxl, ofxparse and qifparse. Before
installing the module install the python
package
first. The required python packages can be installed using the
following commands.
<br>
<br />
<code class="d-block mt-2"
style="background-color:#4e4e4e;color: #fff; border-radius: 5px; padding: 6px 8px;">pip
install openpyxl</code>
<code class="d-block mt-2"
style="background-color:#4e4e4e;color: #fff;padding: 6px 8px; border-radius: 5px;">pip
install ofxparse</code>
<code class="d-block mt-2"
style="background-color:#4e4e4e;color: #fff;padding: 6px 8px; border-radius: 5px;">pip
install qifparse</code>
<br>
<br />
If you are running Odoo in a Docker environment, you can safely install the qifparse package by following the steps below:
<br>
1. Create a custom Dockerfile (e.g. Dockerfile.odoo) with the following content:
<code class="d-block mt-2"
style="background-color:#4e4e4e;color: #fff; border-radius: 5px; padding: 6px 8px;">FROM odoo:18.0<br />
USER root<br />
# Install qifparse to a custom directory<br />
RUN pip3 install --target=/opt/qiflibs qifparse<br />
# Add that directory to PYTHONPATH<br />
ENV PYTHONPATH="/opt/qiflibs:$PYTHONPATH"<br />
USER odoo</code>
2.Update your docker-compose.yml to use this custom Dockerfile for the web service:
<code class="d-block mt-2"
style="background-color:#4e4e4e;color: #fff;padding: 6px 8px; border-radius: 5px;">services:<br />
&nbsp;&nbsp;web:<br />
&nbsp;&nbsp;&nbsp;&nbsp;build:<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;context: .<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dockerfile: Dockerfile.odoo<br />
</code>
3. Rebuild and restart the containers:
<code class="d-block mt-2"
style="background-color:#4e4e4e;color: #fff;padding: 6px 8px; border-radius: 5px;">docker-compose build<br />
docker-compose up -d
</code>
</div>
</div>
</div>
</section>
<!-- demo-main -->
<div style="background-color: #ECE8F2; padding: 40px; border-radius: 12px; font-family: Arial, sans-serif; text-align: center; color: #000; margin-top: 34px;">
<h1 style="font-size: 32px; font-weight: bold; margin-bottom: 30px;">
@ -1417,6 +1477,102 @@
</div>
</div>
</div>
<div class="position-relative mb-4"
style="border-radius:10px; background-color:#f4f4f4">
<div class="p-md-5 p-3 position-relative">
<div class="row">
<div class="col-md-12">
<h1 style="font-weight:bold; font-size:calc(1.1rem + 1vw); line-height:120%; text-align:center; text-transform:capitalize; font-size: 40px;
font-weight: 700;">
<span style="color:#121212; font-size:calc(1.1rem + 1vw)">
A quick option to access Customer Statements in Contacts
</span>
<span style="color: var(--primary-color); font-size:calc(1.1rem + 1vw)"></span>
</h1>
</div>
<div class="col-md-12 mb-4">
<p style="font-weight:400; font-size:16px; line-height:150%; text-align:center; color:var(--text-color-light)">
View and manage all customer statements directly from the contact’s form view.
</p>
</div>
<div class="col-md-12 text-center">
<div class="d-inline-block p-3 shadow-sm"
style="background-color:#fff; border-radius:10px">
<img alt="" class="img-fluid"
loading="lazy"
src="./assets/screenshots/customer_statement.png"
style="min-height: 1px;">
</div>
</div>
</div>
</div>
</div>
<div class="position-relative mb-4"
style="border-radius:10px; background-color:#f4f4f4">
<div class="p-md-5 p-3 position-relative">
<div class="row">
<div class="col-md-12">
<h1 style="font-weight:bold; font-size:calc(1.1rem + 1vw); line-height:120%; text-align:center; text-transform:capitalize; font-size: 40px;
font-weight: 700;">
<span style="color:#121212; font-size:calc(1.1rem + 1vw)">
<!-- A quick option to access Customer Statements in Contacts-->
Payment Statement Report
</span>
<span style="color: var(--primary-color); font-size:calc(1.1rem + 1vw)"></span>
</h1>
</div>
<div class="col-md-12 mb-4">
<p style="font-weight:400; font-size:16px; line-height:150%; text-align:center; color:var(--text-color-light)">
Generate, download, and email customer statements as PDF reports with button clicks directly from the contact’s form view
</p>
</div>
<div class="col-md-12 text-center">
<div class="d-inline-block p-3 shadow-sm"
style="background-color:#fff; border-radius:10px">
<img alt="" class="img-fluid"
loading="lazy"
src="./assets/screenshots/customer_statement_pdf.png"
style="min-height: 1px;">
</div>
</div>
</div>
</div>
<div class="position-relative mb-4"
style="border-radius:10px; background-color:#f4f4f4">
<div class="p-md-5 p-3 position-relative">
<div class="row">
<div class="col-md-12">
<h1 style="font-weight:bold; font-size:calc(1.1rem + 1vw); line-height:120%; text-align:center; text-transform:capitalize; font-size: 40px;
font-weight: 700;">
<span style="color:#121212; font-size:calc(1.1rem + 1vw)">
<!-- A quick option to access Customer Statements in Contacts-->
Payment Statement Report Excel
</span>
<span style="color: var(--primary-color); font-size:calc(1.1rem + 1vw)"></span>
</h1>
</div>
<div class="col-md-12 mb-4">
<p style="font-weight:400; font-size:16px; line-height:150%; text-align:center; color:var(--text-color-light)">
Generate, download, and email customer statements as Excel reports with button clicks directly from the contact’s form view
</p>
</div>
<div class="col-md-12 text-center">
<div class="d-inline-block p-3 shadow-sm"
style="background-color:#fff; border-radius:10px">
<img alt="" class="img-fluid"
loading="lazy"
src="./assets/screenshots/customer_statement_excel.png"
style="min-height: 1px;">
</div>
</div>
</div>
</div>
</div>
</div>
<div aria-labelledby="feature-tab"
class="tab-pane fade show py-1" id="feature"
@ -1646,6 +1802,35 @@
class="tab-pane fade show" id="releases"
role="tabpanel">
<!-- Release Notes -->
<div class="row pt-5 m-0">
<div class="col-md-3">
<h4 style="font-size:16px; font-weight:600; color:#514F4F; margin:0; line-height:26px;">
Latest Release 18.0.5.0.6
</h4>
<span style="font-size:14px; color:#7A7979; display:block; margin-bottom:20px;">
13th Oct, 2025
</span>
</div>
<div class="col-md-8">
<div style="padding:0 0 40px">
<div style="margin:0 0 10px">
<div style="display:inline-block; padding:0px 8px; color:#514F4F; background-color:#FFD8D8; border-radius:20px">
Updt
</div>
</div>
<div class="d-flex m-0"
style="color:#7A7979;">
<ul class="pl-3 mb-0">
<li>
Added Customer Statement feature.
</li>
</ul>
</div>
</div>
<div style="padding:0 0 0; border-bottom:1px solid #E3E3E3">
</div>
</div>
</div>
<div class="row pt-5 m-0">
<div class="col-md-3">
<h4 style="font-size:16px; font-weight:600; color:#514F4F; margin:0; line-height:26px;">

16
base_accounting_kit/static/src/js/action_manager.js

@ -0,0 +1,16 @@
/** @odoo-module*/
import {registry} from "@web/core/registry";
import {download} from "@web/core/network/download";
import { BlockUI, unblockUI } from "@web/core/ui/block_ui";
// Action manager for xlsx report
registry.category('ir.actions.report handlers').add('xlsx', async (action) => {
if (action.report_type === 'xlsx'){
BlockUI;
await download({
url : '/xlsx_report',
data : action.data,
error : (error) => self.call('crash_manager', 'rpc_error', error),
complete: () => unblockUI,
});
}
})

93
base_accounting_kit/views/res_partner_views.xml

@ -0,0 +1,93 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<!-- Customer and vendor statements -->
<record id="view_partner_form" model="ir.ui.view">
<field name="name">res.partner.view.form.inherit.base.account.report
</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="arch" type="xml">
<xpath expr="//page[@name='sales_purchases']" position="after">
<page name="customer_statement_page"
string="Customer Statement">
<button name="action_print_pdf" type="object"
class="btn-secondary">
Print PDF
</button>
<button name="action_print_xlsx" type="object"
class="btn-secondary">
Print Excel
</button>
<button name="action_share_pdf" type="object"
class="btn-secondary">
Sent PDF By Email
</button>
<button name="action_share_xlsx" type="object"
class="btn-secondary">
Sent Excel By Email
</button>
<br/>
<br/>
<field name="customer_report_ids">
<list create='false' delete="false">
<field name="currency_id" column_invisible="1"/>
<field name="invoice_date"
string="Invoice Date"/>
<field name="name" string="Invoice No."/>
<field name="invoice_date_due"/>
<field name="amount_total_signed"
sum="Total Amount" string="Total Amount"
widget="monetary"
options="{'currency_field': 'currency_id'}"/>
<field name="amount_residual_signed"
string="Amount Due" widget="monetary"
options="{'currency_field': 'currency_id'}"/>
<field name="amount_residual" sum="Balance Due"
string="Balance" widget="monetary"
options="{'currency_field': 'currency_id'}"/>
</list>
</field>
</page>
<!-- <page name="supplier_statement"-->
<!-- string="Supplier Statement">-->
<!-- <button name="action_vendor_print_pdf" type="object"-->
<!-- class="btn-secondary">-->
<!-- Print PDF-->
<!-- </button>-->
<!-- <button name="action_vendor_print_xlsx" type="object"-->
<!-- class="btn-secondary">-->
<!-- Print Excel-->
<!-- </button>-->
<!-- <button name="action_vendor_share_pdf" type="object"-->
<!-- class="btn-secondary">-->
<!-- Sent PDF By Email-->
<!-- </button>-->
<!-- <button name="action_vendor_share_xlsx" type="object"-->
<!-- class="btn-secondary">-->
<!-- Sent Excel By Email-->
<!-- </button>-->
<!-- <br/>-->
<!-- <br/>-->
<!-- <field name="vendor_statement_ids">-->
<!-- <list create="false" delete="false">-->
<!-- <field name="currency_id" column_invisible="1"/>-->
<!-- <field name="invoice_date" string="Bill Date"/>-->
<!-- <field name="name" string="Bill No."/>-->
<!-- <field name="invoice_date_due"/>-->
<!-- <field name="amount_total_signed"-->
<!-- sum="Total Amount" string="Total Amount"-->
<!-- widget="monetary"-->
<!-- options="{'currency_field': 'currency_id'}"/>-->
<!-- <field name="amount_residual_signed"-->
<!-- string="Amount Due" widget="monetary"-->
<!-- options="{'currency_field': 'currency_id'}"/>-->
<!-- <field name="amount_residual" sum="Balance Due"-->
<!-- string="Balance" widget="monetary"-->
<!-- options="{'currency_field': 'currency_id'}"/>-->
<!-- </list>-->
<!-- </field>-->
<!-- </page>-->
</xpath>
</field>
</record>
</odoo>
Loading…
Cancel
Save