first push message

This commit is contained in:
2026-07-01 14:41:49 +07:00
parent 6667dec2bf
commit 58b5f46cc4
2951 changed files with 316619 additions and 0 deletions
@@ -0,0 +1,129 @@
/** @odoo-module **/
import { registry } from "@web/core/registry";
import { useService } from "@web/core/utils/hooks";
import { View } from "@web/views/view";
import { kanbanView } from "@web/views/kanban/kanban_view";
import { KanbanController } from "@web/views/kanban/kanban_controller";
import { KanbanRenderer } from "@web/views/kanban/kanban_renderer";
import { KanbanRecord } from "@web/views/kanban/kanban_record";
const { useState } = owl;
class CustomKanbanController extends KanbanController {
async setup(){
super.setup()
this.state = useState({
selectedStLineId: null,
linesWidgetData: null,
moveLineData: null,
});
this.action = useService("action")
this.orm = useService("orm")
const o_bank_reconcile_status_buttons_aside_left = document.getElementsByClassName("o_bank_reconcile_status_buttons_aside_left")
}
async openRecord(record, mode) {
this.state.moveLineData = null;
this.state.viewID = await this.orm.call('res.config.settings', 'get_view_id', [])
await this.mountStLine(record.resId);
const statementRecord = document.querySelectorAll('.o_bank_reconcile_st_line_kanban_card');
statementRecord.forEach(line => {
line.addEventListener('click', async (event) => {
// Remove 'div-added' class and its child divs from all elements
statementRecord.forEach(item => {
item.classList.remove('div-added');
const childDiv = item.querySelector('.new-div');
if (childDiv) {
item.removeChild(childDiv);
}
});
// Add 'div-added' class and new div to the clicked record
if (!line.classList.contains('div-added')) {
const newDiv = document.createElement('div');
newDiv.classList.add('new-div'); // Add a class to identify the new div
line.classList.add('div-added');
line.appendChild(newDiv);
}
});
});
}
async mountStLine(stLineId){
const currentStLineId = this.state.selectedStLineId;
if (currentStLineId !== stLineId) {
this.state.selectedStLineId = stLineId; // Update selected ST Line ID
try {
const data = await this.orm.call("account.bank.statement.line", "get_statement_line", [stLineId]);
this.state.linesWidgetData = data;
} catch (error) {
console.error("Error fetching statement line data:", error);
}
try {
const data = await this.orm.call('account.bank.statement.line', 'read', [[stLineId]], { fields: ['lines_widget_json'] });
if (data && data.length > 0 && data[0].lines_widget_json) {
const parsedData = JSON.parse(data[0].lines_widget_json);
const moveIdMatch = parsedData.move_id.match(/\((\d+),\)/);
parsedData.numeric_move_id = moveIdMatch ? parseInt(moveIdMatch[1]) : null;
this.state.moveLineData = parsedData;
} else {
console.warn("No lines_widget_json found for selected statement line.");
}
} catch (error) {
console.error("Error reading statement line:", error);
}
}
}
get prepareFormPropsBankReconcile(){
if (!this.state.selectedStLineId) {
return null; // or some default props
}
return {
type: "form",
viewId: this.state.viewID,
context: {
default_st_line_id: this.state.selectedStLineId,
default_lines_widget: this.state.linesWidgetData || null,
default_move_line: this.state.moveLineData || null,
},
display: { controlPanel: false, noBreadcrumbs: true},
mode: "edit",
resModel: "account.bank.statement.line",
resId: this.state.selectedStLineId,
}
}
}
CustomKanbanController.components = {
...CustomKanbanController.components, View }
CustomKanbanController.template = "base_accounting_kit.CustomKanbanView";
export class BankCustomKanbanRenderer extends KanbanRenderer {
setup(){
super.setup();
}
}
export class BankReconcileKanbanRecord extends KanbanRecord {
setup(){
super.setup();
this.state=useState({
Statement_record:{}
})
}
}
BankReconcileKanbanRecord.template = "base_accounting_kit.BankReconcileKanbanRecord";
BankCustomKanbanRenderer.components = {
...KanbanRenderer.components,
KanbanRecord: BankReconcileKanbanRecord,
}
BankCustomKanbanRenderer.template = "base_accounting_kit.BankRecKanbanRenderer";
export const customKanbanView = {
...kanbanView,
Controller: CustomKanbanController,
Renderer: BankCustomKanbanRenderer,
searchMenuTypes: ["filter"],
};
// Register it to the views registry
registry.category("views").add("custom_kanban", customKanbanView);
@@ -0,0 +1,168 @@
/** @odoo-module **/
import { registry } from '@web/core/registry';
import { ListController } from "@web/views/list/list_controller";
import { listView } from "@web/views/list/list_view";
import { useState, useRef } from "@odoo/owl";
import { useListener, useService} from "@web/core/utils/hooks";
export class AccountMoveLineListController extends ListController {
constructor() {
super(...arguments);
this.resIdList = [];
}
setup(){
super.setup();
this.state = useState({ selectedRecordId: null ,
selectedRecordIds: [],});
this.action = useService("action")
this.orm = useService("orm")
}
async openRecord(record) {
const kanban_row = this.__owl__.bdom.parentEl.ownerDocument.querySelector(`tr[data-id]`);
const data_id = parseInt(kanban_row.getAttribute('data-id'))
var data = await this.orm.call('account.bank.statement.line',
'update_match_row_data',
[record.resId])
await this.orm.call('account.bank.statement.line', 'write', [[data_id], { lines_widget_json: JSON.stringify(data) }]);
const rowSelector = this.__owl__.bdom.parentEl.querySelector(`tr[data-id='${record.id}']`)
if (!record.clickCount) {
record.clickCount = true
rowSelector.style.backgroundColor = "#d1ecf1";
} else {
// Set the default background color here
record.clickCount = false;
rowSelector.style.backgroundColor = "white";
}
const currencySymbol = await this.orm.call('res.currency', 'read',[record.data.currency_id.id])
const mainKanbanDiv = this.__owl__.bdom.parentEl.ownerDocument.querySelector('#base_accounting_reconcile')
const existingRow = this.__owl__.bdom.parentEl.ownerDocument.querySelector(`tr[data-resId="${record.resId}"]`)
const stateLineRow = this.__owl__.bdom.parentEl.ownerDocument.querySelector('.statement_row')
if (stateLineRow){
const dataIdValue = stateLineRow.getAttribute('data-id');
if(dataIdValue == record.resId){
mainKanbanDiv.removeChild(stateLineRow);
}
}
if (existingRow) {
mainKanbanDiv.removeChild(existingRow);
} else {
// If the row doesn't exist, create and add it
const dateObject = new Date(record.data.date);
const year = dateObject.getFullYear();
const month = String(dateObject.getMonth() + 1).padStart(2, '0'); // Months are 0-indexed
const day = String(dateObject.getDate()).padStart(2, '0');
const formattedDate = `${year}-${month}-${day}`;
let amount = parseFloat(record.data.amount_residual);
let debitColumn = '';
let creditColumn = '';
let partnerName = '';
let moveId = '';
// Check if partner_id exists and is not empty
if (record.data.partner_id && record.data.partner_id.display_name) {
partnerName = record.data.partner_id.display_name;
}
if (record.data.move_id && record.data.move_id.display_name) {
moveId = `<br/><span id="moveLine" style="font-size: 12px; font-style: italic;font-weight: normal;color: #01666b;cursor: pointer;" data-moveId="${record.data.move_id[0]}">${record.data.move_id.display_name}</span>`;
}
if (amount < 0) {
debitColumn = `<td style="font-weight: bold; display: table-cell; max-width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; vertical-align: top;">${currencySymbol[0].symbol} ${-amount}</td>`;
} else {
creditColumn = `<td style="font-weight: bold; display: table-cell; max-width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; vertical-align: top;">${currencySymbol[0].symbol} ${amount}</td>`;
}
const newRow = document.createElement('tr');
newRow.setAttribute('data-resId', record.resId); // Set a unique identifier for the row
console.log(record, "record")
if (debitColumn !== '') {
newRow.innerHTML = `<td style="font-weight: bold; display: table-cell; max-width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; vertical-align: top;">${record.data.account_id.display_name}
${moveId}<span style="font-size: 12px;font-style: italic;font-weight: normal;"> : ${record.data.name}</span></td>
<td style="font-weight: bold; display: table-cell; max-width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; vertical-align: top;">${partnerName}</td>
<td style="font-weight: bold; display: table-cell; max-width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; vertical-align: top;">${formattedDate}</td>
${debitColumn}
<td style="font-weight: bold; display: table-cell; max-width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; vertical-align: top;"> </td>
<td class="o_list_remove_record">
<button class="btn fa fa-trash-o" data-resId="${record.resId}"/>
</td>`;
} else if (creditColumn !== '') {
newRow.innerHTML = `<td style="font-weight: bold; display: table-cell; max-width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; vertical-align: top;">${record.data.account_id.display_name}
${moveId}<span style="font-size: 12px;font-style: italic;font-weight: normal;"> : ${record.data.name}</span></td>
<td style="font-weight: bold; display: table-cell; max-width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; vertical-align: top;">${partnerName}</td>
<td style="font-weight: bold; display: table-cell; max-width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; vertical-align: top;">${formattedDate}</td>
<td style="font-weight: bold; display: table-cell; max-width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; vertical-align: top;"> </td>
${creditColumn}
<td class="o_list_remove_record">
<button class="btn fa fa-trash-o" data-resId="${record.resId}"/>
</td>`;
}
newRow.addEventListener('click', async () => {
const allRows = this.__owl__.bdom.parentEl.ownerDocument.querySelectorAll('tr[data-resId]');
allRows.forEach(row => {
row.classList.remove('selected-row');
});
newRow.classList.add('selected-row');
if (record.resId){
const manualOpsTab = this.__owl__.bdom.parentEl.ownerDocument.querySelector('[name="manual_operations_tab"]');
if (manualOpsTab) {
manualOpsTab.click();
const accountField = this.__owl__.bdom.parentEl.ownerDocument.querySelector('[name="account_id"]');
accountField.value = record.data.account_id.display_name;
}
}
});
// Append the new row to the mainKanbanDiv
mainKanbanDiv.appendChild(newRow);
const deleteButtons = this.__owl__.bdom.parentEl.ownerDocument.querySelectorAll('.fa-trash-o');
deleteButtons.forEach(button => {
button.addEventListener('click', async (event) => {
const resId = event.target.getAttribute('data-resId');
await this.removeRecord(resId);
});
});
const moveLine = this.__owl__.bdom.parentEl.ownerDocument.querySelectorAll('#moveLine');
moveLine.forEach(line => {
line.addEventListener('click',async (event) => {
const moveId = event.target.getAttribute('data-moveId');
await this.ShowMoveForm(moveId);
});
});
}
this.updateResIdList();
}
async removeRecord(resId){
const mainKanbanDiv = this.__owl__.bdom.parentEl.ownerDocument.querySelector('#base_accounting_reconcile');
const rowToRemove = this.__owl__.bdom.parentEl.ownerDocument.querySelector(`tr[data-resId="${resId}"]`);
if (rowToRemove) {
mainKanbanDiv.removeChild(rowToRemove);
this.updateResIdList();
}
}
async ShowMoveForm(moveId) {
// Convert moveId from string to integer
const moveIdInt = parseInt(moveId, 10);
// Check if the conversion is successful
if (!isNaN(moveIdInt)) {
this.action.doAction({
type: 'ir.actions.act_window',
res_model: 'account.move',
res_id: moveIdInt,
views: [[false, "form"]],
target: 'current',
});
}
}
updateResIdList() {
// Get all resId values from the current rows and update the resIdList array
const rows = this.__owl__.bdom.parentEl.ownerDocument.querySelectorAll('tr[data-resId]');
this.resIdList = Array.from(rows).map(row => parseInt(row.getAttribute('data-resId'), 10));
}
}
AccountMoveLineListController.template = 'base_accounting_kit.AccountMoveLineListController';
export const AccountMoveListView = {
...listView,
Controller: AccountMoveLineListController,
};
registry.category('views').add('account_move_line_list_controller', AccountMoveListView);
@@ -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,
});
}
})
@@ -0,0 +1,79 @@
/** @odoo-module **/
import { registry } from '@web/core/registry';
import { useService } from "@web/core/utils/hooks";
import { _t } from "@web/core/l10n/translation";
const { Component, useState, useExternalListener } = owl;
export class BankReconcileFormLinesWidget extends Component {
setup(){
super.setup();
this.state = useState({statementLineResult: null,
MoveLineResult:null});
this.action = useService("action")
this.orm = useService("orm")
}
range(n){
return[...Array(Math.max(n,0)).keys()];
}
get record(){
return this.props.record;
}
async mountStatementLine(ev){
const manualOpsTab = document.querySelector('[name="manual_operations_tab"]');
if (manualOpsTab) {
manualOpsTab.click();
}
}
async onclickLink(ev){
const id = ev.currentTarget.dataset.id;
if (id) {
this.action.doAction({
type: 'ir.actions.act_window',
res_model: 'account.move',
res_id: parseInt(id),
views: [[false, "form"]],
target: 'current',
});
}
}
async removeRecord(ev){
ev.preventDefault();
var button = ev.currentTarget;
var row = button.closest('tr');
var firstRow = document.querySelector('.o_data_row:first-child');
var data_id = firstRow.dataset.id;
try {
await this.orm.call('account.bank.statement.line', 'write', [[parseInt(data_id)], {'lines_widget_json': null}]);
// Update the UI or perform any other actions as needed
} catch (error) {
console.error('Error removing lines_widget_json:', error);
// Handle the error as needed
}
row.remove();
}
getRenderValues(){
var self=this;
let data = this.props.record.context
this.orm.call('account.bank.statement.line', 'update_rowdata', [this.props.record.data.id])
let columns=[
["account",_t("Account")],
["partner",_t("Partner")],
["date",_t("Date")],
];
if(data.display_analytic_account_column){
columns.push(["analytic_account", _t("Analytic Account")]);
}
if(data.display_multi_currency_column){
columns.push(["amount_currency", _t("Amount in Currency")], ["currency", _t("Currency")]);
}
if(data.display_taxes_column){
columns.push(["taxes", _t("Taxes")]);
}
columns.push(["debit", _t("Debit")], ["credit", _t("Credit")], ["__trash", ""]);
return {...data,columns:columns}
}
}
BankReconcileFormLinesWidget.template = 'base_accounting_kit.bank_reconcile_widget_lines_widget';
export const FormLines = {
component: BankReconcileFormLinesWidget
}
registry.category("fields").add('bank_reconcile_widget_lines_widget', FormLines)
@@ -0,0 +1,124 @@
/** @odoo-module **/
import { registry } from '@web/core/registry';
import { standardWidgetProps } from "@web/views/widgets/standard_widget_props";
import { View } from "@web/views/view";
const { Component, useSubEnv } = owl;
class FormListView extends Component {
setup(){
useSubEnv({
config:{},
});
}
get bankReconcileListViewProps(){
return{
type:'list',
display:{
controlPanel:{
"top-left":false,
"bottom-left":false,
}
},
resModel:this.props.resModel,
searchMenuTypes:["filter"],
allowSelectors:false,
searchViewId:false,
searchViewArch: `
<search>
<field name="name" string="Journal Item"/>
<field name="journal_id"/>
<field name="account_id"/>
<field name="partner_id"/>
<field name="move_id"/>
<field name="currency_id" groups="base.group_multi_currency"/>
<field name="date" string="Date"/>
<separator/>
<filter name="amount_received" string="Incoming" domain="[('balance','>',0.0)]"/>
<filter name="amount_paid" string="Outgoing" domain="[('balance','&lt;',0.0)]"/>
<separator name="inject_after"/>
<filter name="date" string="Date" date="date"/>
<filter string="Customer/Vendor" name="partner_id" domain="[]"/>
<filter string="Miscellaneous" domain="[('journal_id.type', '=', 'general')]" name="misc_filter"/>
</search>
`,
searchViewFields: {
name: {
name:"name",
string:"Journal Item",
type:"char",
store: true,
sortable: true,
searchable: true,
},
date: {
name: "date",
string: "Date",
type: "date",
store: true,
sortable: true,
searchable: true,
},
journal_id: {
name: "journal_id",
string: "Journal",
type: "many2one",
store: true,
sortable: true,
searchable: true,
},
account_id: {
name: "account_id",
string: "Account",
type: "many2one",
store: true,
sortable: true,
searchable: true,
},
partner_id: {
name: "partner_id",
string: "Partner",
type: "many2one",
store: true,
sortable: true,
searchable: true,
group_by:"partner_id",
},
currency_id: {
name: "currency_id",
string: "Currency",
type: "many2one",
store: true,
sortable: true,
searchable: true,
},
move_id: {
name:"move_id",
string:"Journal Entry",
type:"many2one",
store:true,
sortable:true,
searchable:true,
filter_domain:"['|',('move_id.name','ilike',self),('move_id.ref','ilike',self)]",
},
},
context:{
list_view_ref:"base_accounting_kit.account_move_line_view_tree",
}
}
}
}
FormListView.template = "base_accounting_kit.FormListView";
FormListView.components = { View };
FormListView.props = {
...standardWidgetProps,
resModel: { type: String },
};
export const formListView = {
component: FormListView,
extractProps: ({ attrs }) => ({
resModel: attrs.resModel,
}),
};
registry.category("view_widgets").add("form_list_view", formListView);
@@ -0,0 +1,7 @@
.kanbanwidth{
width:100px !important;
}
.o_custom_kanban_view .o_content{
display:flex;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,33 @@
<templates>
<t t-name="base_accounting_kit.CustomKanbanView" t-inherit="web.KanbanView"
owl="1">
<xpath expr="//Layout" position="inside">
<div class="h-100 form_view_class">
<View t-if="state.selectedStLineId"
t-props="prepareFormPropsBankReconcile"
t-key="state.selectedStLineId"/>
</div>
</xpath>
</t>
<t t-name="base_accounting_kit.BankRecKanbanRenderer"
t-inherit="web.KanbanRenderer" t-inherit-mode="primary" owl="1">
<xpath expr="//div[hasclass('o_kanban_renderer')]"
position="before">
</xpath>
<xpath expr="//div[hasclass('o_kanban_renderer')]"
position="attributes">
<attribute name="class">o_kanban_renderer o_custom_class</attribute>
</xpath>
<xpath expr="//div[hasclass('o_custom_class')]"
position="attributes">
<attribute name="style">width:30%;</attribute>
</xpath>
</t>
<t t-name="base_accounting_kit.BankReconcileKanbanRecord"
t-inherit="web.KanbanRecord" t-inherit-mode="primary" owl="1">
</t>
<t t-name="base_accounting_kit.AccountMoveLineListController"
t-inherit="web.ListView"
t-inherit-mode="primary" owl="1">
</t>
</templates>
@@ -0,0 +1,124 @@
<?xml version="1.0" encoding="utf-8" ?>
<templates id="template" xml:space="preserve">
<t t-name="base_accounting_kit.bank_reconcile_widget_lines_widget">
<t t-set="data" t-value="getRenderValues()"/>
<div class="o_list_renderer table-responsive">
<table class="table table-sm position-relative mb-0 o_list_table_ungrouped table-striped o_bank_reconcile_lines_widget_line">
<thead>
<tr>
<t t-foreach="data.columns" t-as="column"
t-key="column[0]">
<th t-esc="column[1]"/>
</t>
</tr>
</thead>
<tbody id="base_accounting_reconcile">
<t t-if="data.default_lines_widget">
<t t-foreach="data.default_lines_widget" t-as="line"
t-key="line.id">
<tr class="o_data_row"
t-on-click="mountStatementLine"
t-att-data-id="line.id">
<t t-set="account"
t-value="line.account_code+' '+line.account_name"/>
<t t-set="amount"
t-value="line.currency_symbol+' '+line.amount"/>
<td class="o_data_cell o_field_cell"
style="font-weight: bolder;"
field="account_id">
<t t-esc="account"/>
<br/>
<span class="text-muted"
style="font-style: italic;">
<t t-esc="line.payment_ref"/>
</span>
</td>
<t t-if="line.partner_id">
<td field="partner_id"
t-esc="line.partner_id[1]"
style="font-weight: bolder;"/>
</t>
<t t-else="">
<td t-esc="line.partner_id[1]"/>
</t>
<td field="date"
t-esc="line.date"
style="font-weight: bolder;"/>
<t t-if="line.amount &gt; 0">
<td field="debit"
t-esc="amount"
style="font-weight: bolder;"/>
</t>
<t t-else="">
<td t-esc="' '"/>
</t>
<t t-if="line.amount &lt; 0">
<td field="credit"
t-esc="amount"
style="font-weight: bolder;"/>
</t>
</tr>
</t>
</t>
<t t-if="data.default_move_line">
<tr class="o_data_row statement_row"
t-on-click="mountStatementLine"
t-att-data-id="data.default_move_line.id">
<t t-set="amount_residual"
t-value="data.default_move_line.currency_symbol+' '+data.default_move_line.amount_residual"/>
<t t-set="move_account"
t-value="data.default_move_line.account_code+' '+data.default_move_line.account_name"/>
<td class="o_data_cell o_field_cell"
style="font-weight: bolder;">
<t t-esc="move_account"/>
<br/>
<span style="font-size: 12px; font-style: italic;font-weight: normal;color: #01666b;cursor: pointer;"
t-att-data-id="data.default_move_line.numeric_move_id"
t-on-click="onclickLink">
<t t-esc="data.default_move_line.move_name"/>
</span>
:
<span class="text-muted"
style="font-style: italic;">
<t t-esc="data.default_move_line.name"/>
</span>
</td>
<t t-if="data.default_move_line.partner_id">
<td t-esc="data.default_move_line.partner_name"
style="font-weight: bolder;"/>
</t>
<t t-else="">
<td t-esc="data.default_move_line.partner_name"/>
</t>
<td t-esc="data.default_move_line.date"
style="font-weight: bolder;"/>
<t t-if="data.default_move_line.amount_residual &lt; 0">
<td t-esc="amount_residual"
style="font-weight: bolder;"/>
</t>
<t t-else="">
<td t-esc="' '"/>
</t>
<t t-if="data.default_move_line.amount_residual &gt; 0">
<td t-esc="amount_residual"
style="font-weight: bolder;"/>
</t>
<t t-if="this.props.record.data.bank_state!='reconciled'">
<td class="o_list_remove_record">
<button class="btn fa fa-trash-o"
style="margin-top: -4px;"
t-on-click="removeRecord"/>
</td>
</t>
</tr>
</t>
</tbody>
</table>
</div>
</t>
<t t-name="base_accounting_kit.FormListView" owl="1">
<t t-if="props.record.id">
<View t-props="bankReconcileListViewProps"/>
</t>
</t>
</templates>