feat: ADP Export Files menu with filestore storage, remove Sync All button
- Add fusion_claims.adp.export.record model with filestore-backed Binary field for tracking exported ADP claims files organized by Year > Month > Posting Period - Add tree/form/search views with default group-by hierarchy, latest first - Add "Export Files" menuitem under ADP menu section - Add bulk ZIP download server action for multi-select export - Replace Documents app storage with new model in export wizard - Remove Documents-related methods (_save_to_documents, folder creation) - Add migration button in Settings to move existing Documents files - Fix Export ADP button visibility: only show on ADP portion invoices - Remove redundant Sync All button from invoice form - Add ACL entries for billing users (read/create) and managers (full CRUD) - Bump version to 19.0.7.3.0 Made-with: Cursor
This commit is contained in:
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
{
|
{
|
||||||
'name': 'Fusion Claims',
|
'name': 'Fusion Claims',
|
||||||
'version': '19.0.7.2.0',
|
'version': '19.0.7.3.0',
|
||||||
'category': 'Sales',
|
'category': 'Sales',
|
||||||
'summary': 'Complete ADP Claims Management with Dashboard, Sales Integration, Billing Automation, and Two-Stage Verification.',
|
'summary': 'Complete ADP Claims Management with Dashboard, Sales Integration, Billing Automation, and Two-Stage Verification.',
|
||||||
'description': """
|
'description': """
|
||||||
@@ -134,6 +134,7 @@
|
|||||||
'views/dashboard_views.xml',
|
'views/dashboard_views.xml',
|
||||||
'views/client_profile_views.xml',
|
'views/client_profile_views.xml',
|
||||||
'wizard/xml_import_wizard_views.xml',
|
'wizard/xml_import_wizard_views.xml',
|
||||||
|
'views/adp_export_record_views.xml',
|
||||||
'views/adp_claims_views.xml',
|
'views/adp_claims_views.xml',
|
||||||
'views/submission_history_views.xml',
|
'views/submission_history_views.xml',
|
||||||
'views/product_template_adp_views.xml',
|
'views/product_template_adp_views.xml',
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
# Part of the Fusion Claim Assistant product family.
|
# Part of the Fusion Claim Assistant product family.
|
||||||
|
|
||||||
from . import adp_posting_schedule
|
from . import adp_posting_schedule
|
||||||
|
from . import adp_export_record
|
||||||
from . import res_company
|
from . import res_company
|
||||||
from . import res_config_settings
|
from . import res_config_settings
|
||||||
from . import fusion_central_config
|
from . import fusion_central_config
|
||||||
|
|||||||
344
fusion_claims/models/adp_export_record.py
Normal file
344
fusion_claims/models/adp_export_record.py
Normal file
@@ -0,0 +1,344 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2024-2025 Nexa Systems Inc.
|
||||||
|
# License OPL-1 (Odoo Proprietary License v1.0)
|
||||||
|
# Part of the Fusion Claim Assistant product family.
|
||||||
|
|
||||||
|
import io
|
||||||
|
import re
|
||||||
|
import base64
|
||||||
|
import logging
|
||||||
|
import zipfile
|
||||||
|
from datetime import date, datetime
|
||||||
|
|
||||||
|
from odoo import models, fields, api, _
|
||||||
|
from odoo.exceptions import UserError
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ADPExportRecord(models.Model):
|
||||||
|
_name = 'fusion_claims.adp.export.record'
|
||||||
|
_description = 'ADP Export File Record'
|
||||||
|
_inherit = ['fusion_claims.adp.posting.schedule.mixin']
|
||||||
|
_order = 'export_date desc, id desc'
|
||||||
|
|
||||||
|
name = fields.Char(
|
||||||
|
string='Filename',
|
||||||
|
required=True,
|
||||||
|
index=True,
|
||||||
|
)
|
||||||
|
export_date = fields.Datetime(
|
||||||
|
string='Export Date',
|
||||||
|
required=True,
|
||||||
|
default=fields.Datetime.now,
|
||||||
|
index=True,
|
||||||
|
)
|
||||||
|
posting_period_date = fields.Date(
|
||||||
|
string='Posting Period',
|
||||||
|
required=True,
|
||||||
|
index=True,
|
||||||
|
help='The ADP posting date this export targets',
|
||||||
|
)
|
||||||
|
posting_period_label = fields.Char(
|
||||||
|
string='Posting Period Label',
|
||||||
|
compute='_compute_period_fields',
|
||||||
|
store=True,
|
||||||
|
)
|
||||||
|
year = fields.Char(
|
||||||
|
string='Year',
|
||||||
|
compute='_compute_period_fields',
|
||||||
|
store=True,
|
||||||
|
index=True,
|
||||||
|
)
|
||||||
|
month = fields.Char(
|
||||||
|
string='Month',
|
||||||
|
compute='_compute_period_fields',
|
||||||
|
store=True,
|
||||||
|
)
|
||||||
|
month_number = fields.Integer(
|
||||||
|
string='Month #',
|
||||||
|
compute='_compute_period_fields',
|
||||||
|
store=True,
|
||||||
|
help='Numeric month for proper ordering',
|
||||||
|
)
|
||||||
|
|
||||||
|
file_data = fields.Binary(
|
||||||
|
string='Export File',
|
||||||
|
attachment=True,
|
||||||
|
help='The exported ADP claims file (stored in filestore)',
|
||||||
|
)
|
||||||
|
filename = fields.Char(
|
||||||
|
string='Download Filename',
|
||||||
|
)
|
||||||
|
invoice_ids = fields.Many2many(
|
||||||
|
'account.move',
|
||||||
|
'adp_export_record_invoice_rel',
|
||||||
|
'export_id',
|
||||||
|
'invoice_id',
|
||||||
|
string='Exported Invoices',
|
||||||
|
)
|
||||||
|
invoice_count = fields.Integer(
|
||||||
|
string='Invoice Count',
|
||||||
|
compute='_compute_invoice_count',
|
||||||
|
)
|
||||||
|
line_count = fields.Integer(
|
||||||
|
string='Lines Exported',
|
||||||
|
default=0,
|
||||||
|
)
|
||||||
|
vendor_code = fields.Char(
|
||||||
|
string='Vendor Code',
|
||||||
|
)
|
||||||
|
user_id = fields.Many2one(
|
||||||
|
'res.users',
|
||||||
|
string='Exported By',
|
||||||
|
default=lambda self: self.env.uid,
|
||||||
|
index=True,
|
||||||
|
)
|
||||||
|
company_id = fields.Many2one(
|
||||||
|
'res.company',
|
||||||
|
string='Company',
|
||||||
|
default=lambda self: self.env.company,
|
||||||
|
index=True,
|
||||||
|
)
|
||||||
|
notes = fields.Text(
|
||||||
|
string='Notes',
|
||||||
|
)
|
||||||
|
|
||||||
|
@api.depends('posting_period_date')
|
||||||
|
def _compute_period_fields(self):
|
||||||
|
for record in self:
|
||||||
|
ppd = record.posting_period_date
|
||||||
|
if ppd:
|
||||||
|
record.year = str(ppd.year)
|
||||||
|
record.month = ppd.strftime('%B')
|
||||||
|
record.month_number = ppd.month
|
||||||
|
record.posting_period_label = ppd.strftime('%b %d, %Y')
|
||||||
|
else:
|
||||||
|
record.year = ''
|
||||||
|
record.month = ''
|
||||||
|
record.month_number = 0
|
||||||
|
record.posting_period_label = ''
|
||||||
|
|
||||||
|
@api.depends('invoice_ids')
|
||||||
|
def _compute_invoice_count(self):
|
||||||
|
for record in self:
|
||||||
|
record.invoice_count = len(record.invoice_ids)
|
||||||
|
|
||||||
|
def action_download(self):
|
||||||
|
"""Download the export file."""
|
||||||
|
self.ensure_one()
|
||||||
|
if not self.file_data:
|
||||||
|
raise UserError(_("No file data available for download."))
|
||||||
|
return {
|
||||||
|
'type': 'ir.actions.act_url',
|
||||||
|
'url': f'/web/content?model={self._name}&id={self.id}'
|
||||||
|
f'&field=file_data&filename_field=filename&download=true',
|
||||||
|
'target': 'self',
|
||||||
|
}
|
||||||
|
|
||||||
|
def action_view_invoices(self):
|
||||||
|
"""Open the list of invoices included in this export."""
|
||||||
|
self.ensure_one()
|
||||||
|
return {
|
||||||
|
'type': 'ir.actions.act_window',
|
||||||
|
'name': _('Exported Invoices'),
|
||||||
|
'res_model': 'account.move',
|
||||||
|
'view_mode': 'list,form',
|
||||||
|
'domain': [('id', 'in', self.invoice_ids.ids)],
|
||||||
|
}
|
||||||
|
|
||||||
|
def action_download_zip(self):
|
||||||
|
"""Download selected export records as a single ZIP file.
|
||||||
|
|
||||||
|
Works as a multi-record action from the list view.
|
||||||
|
"""
|
||||||
|
if not self:
|
||||||
|
raise UserError(_("Please select at least one export record."))
|
||||||
|
|
||||||
|
buf = io.BytesIO()
|
||||||
|
with zipfile.ZipFile(buf, 'w', zipfile.ZIP_DEFLATED) as zf:
|
||||||
|
for record in self:
|
||||||
|
if not record.file_data:
|
||||||
|
continue
|
||||||
|
file_content = base64.b64decode(record.file_data)
|
||||||
|
archive_name = record.filename or record.name
|
||||||
|
zf.writestr(archive_name, file_content)
|
||||||
|
|
||||||
|
zip_data = base64.b64encode(buf.getvalue())
|
||||||
|
buf.close()
|
||||||
|
|
||||||
|
# Create a transient attachment for download
|
||||||
|
today_str = date.today().strftime('%Y-%m-%d')
|
||||||
|
zip_filename = f"ADP_Export_Files_{today_str}.zip"
|
||||||
|
attachment = self.env['ir.attachment'].sudo().create({
|
||||||
|
'name': zip_filename,
|
||||||
|
'type': 'binary',
|
||||||
|
'datas': zip_data,
|
||||||
|
'mimetype': 'application/zip',
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
'type': 'ir.actions.act_url',
|
||||||
|
'url': f'/web/content/{attachment.id}?download=true',
|
||||||
|
'target': 'self',
|
||||||
|
}
|
||||||
|
|
||||||
|
# ======================================================================
|
||||||
|
# MIGRATION: Documents app -> ADP Export Records
|
||||||
|
# ======================================================================
|
||||||
|
@api.model
|
||||||
|
def migrate_from_documents(self):
|
||||||
|
"""Migrate existing ADP export files from Documents app to this model.
|
||||||
|
|
||||||
|
Searches for binary files under 'ADP Billing Files' folder hierarchy,
|
||||||
|
parses filenames to determine vendor code and date, calculates the
|
||||||
|
posting period, and creates export records.
|
||||||
|
|
||||||
|
Returns a notification dict for the UI.
|
||||||
|
"""
|
||||||
|
if 'documents.document' not in self.env:
|
||||||
|
return {
|
||||||
|
'type': 'ir.actions.client',
|
||||||
|
'tag': 'display_notification',
|
||||||
|
'params': {
|
||||||
|
'title': _('Documents App Not Installed'),
|
||||||
|
'message': _('The Documents app is not installed. Nothing to migrate.'),
|
||||||
|
'type': 'warning',
|
||||||
|
'sticky': False,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
Document = self.env['documents.document'].sudo()
|
||||||
|
|
||||||
|
root_folders = Document.search([
|
||||||
|
('name', 'ilike', 'ADP Billing Files'),
|
||||||
|
('type', '=', 'folder'),
|
||||||
|
])
|
||||||
|
|
||||||
|
if not root_folders:
|
||||||
|
return {
|
||||||
|
'type': 'ir.actions.client',
|
||||||
|
'tag': 'display_notification',
|
||||||
|
'params': {
|
||||||
|
'title': _('No Files Found'),
|
||||||
|
'message': _('No "ADP Billing Files" folder found in Documents.'),
|
||||||
|
'type': 'info',
|
||||||
|
'sticky': False,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
all_folder_ids = self._collect_subfolder_ids(Document, root_folders.ids)
|
||||||
|
all_folder_ids.extend(root_folders.ids)
|
||||||
|
|
||||||
|
files = Document.search([
|
||||||
|
('folder_id', 'in', all_folder_ids),
|
||||||
|
('type', '=', 'binary'),
|
||||||
|
])
|
||||||
|
|
||||||
|
if not files:
|
||||||
|
return {
|
||||||
|
'type': 'ir.actions.client',
|
||||||
|
'tag': 'display_notification',
|
||||||
|
'params': {
|
||||||
|
'title': _('No Files Found'),
|
||||||
|
'message': _('No export files found in ADP Billing Files folders.'),
|
||||||
|
'type': 'info',
|
||||||
|
'sticky': False,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
migrated = 0
|
||||||
|
skipped = 0
|
||||||
|
errors = 0
|
||||||
|
|
||||||
|
for doc in files:
|
||||||
|
existing = self.search([('name', '=', doc.name)], limit=1)
|
||||||
|
if existing:
|
||||||
|
skipped += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
vendor_code, file_date = self._parse_export_filename(doc.name)
|
||||||
|
if file_date:
|
||||||
|
posting_date = self._get_current_posting_date(file_date)
|
||||||
|
else:
|
||||||
|
posting_date = self._get_current_posting_date(
|
||||||
|
doc.create_date.date() if doc.create_date else date.today()
|
||||||
|
)
|
||||||
|
|
||||||
|
self.create({
|
||||||
|
'name': doc.name,
|
||||||
|
'filename': doc.name,
|
||||||
|
'file_data': doc.datas,
|
||||||
|
'export_date': doc.create_date or fields.Datetime.now(),
|
||||||
|
'posting_period_date': posting_date,
|
||||||
|
'vendor_code': vendor_code or '',
|
||||||
|
'user_id': doc.create_uid.id if doc.create_uid else self.env.uid,
|
||||||
|
'company_id': self.env.company.id,
|
||||||
|
'notes': f'Migrated from Documents app on {date.today()}',
|
||||||
|
})
|
||||||
|
migrated += 1
|
||||||
|
_logger.info("Migrated ADP export file: %s", doc.name)
|
||||||
|
except Exception as e:
|
||||||
|
errors += 1
|
||||||
|
_logger.error("Failed to migrate document %s: %s", doc.name, e)
|
||||||
|
|
||||||
|
# Archive migrated documents so they don't clutter the Documents app
|
||||||
|
if migrated > 0:
|
||||||
|
try:
|
||||||
|
migrated_docs = files.filtered(
|
||||||
|
lambda d: not self.search([('name', '=', d.name)], limit=1) is None
|
||||||
|
)
|
||||||
|
if hasattr(Document, 'active'):
|
||||||
|
files.write({'active': False})
|
||||||
|
_logger.info("Archived %d documents after migration", len(files))
|
||||||
|
except Exception as e:
|
||||||
|
_logger.warning("Could not archive old documents: %s", e)
|
||||||
|
|
||||||
|
msg = _(
|
||||||
|
"Migration complete: %d files migrated, %d skipped (already exist), %d errors."
|
||||||
|
) % (migrated, skipped, errors)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'type': 'ir.actions.client',
|
||||||
|
'tag': 'display_notification',
|
||||||
|
'params': {
|
||||||
|
'title': _('Migration Complete'),
|
||||||
|
'message': msg,
|
||||||
|
'type': 'success' if errors == 0 else 'warning',
|
||||||
|
'sticky': True,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _collect_subfolder_ids(self, Document, parent_ids):
|
||||||
|
"""Recursively collect all subfolder IDs under the given parent folders."""
|
||||||
|
all_ids = []
|
||||||
|
children = Document.search([
|
||||||
|
('folder_id', 'in', parent_ids),
|
||||||
|
('type', '=', 'folder'),
|
||||||
|
])
|
||||||
|
if children:
|
||||||
|
all_ids.extend(children.ids)
|
||||||
|
all_ids.extend(self._collect_subfolder_ids(Document, children.ids))
|
||||||
|
return all_ids
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _parse_export_filename(self, filename):
|
||||||
|
"""Parse an ADP export filename to extract vendor code and date.
|
||||||
|
|
||||||
|
Expected format: VENDORCODE_YYYY-MM-DD.txt
|
||||||
|
Returns: (vendor_code, date_obj) or (None, None) if unparseable.
|
||||||
|
"""
|
||||||
|
if not filename:
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
match = re.match(r'^(.+?)_(\d{4}-\d{2}-\d{2})\.\w+$', filename)
|
||||||
|
if match:
|
||||||
|
vendor_code = match.group(1)
|
||||||
|
try:
|
||||||
|
file_date = date.fromisoformat(match.group(2))
|
||||||
|
return vendor_code, file_date
|
||||||
|
except ValueError:
|
||||||
|
return vendor_code, None
|
||||||
|
|
||||||
|
return None, None
|
||||||
@@ -641,3 +641,7 @@ class ResConfigSettings(models.TransientModel):
|
|||||||
def action_set_gradient_dark_slate(self):
|
def action_set_gradient_dark_slate(self):
|
||||||
self._apply_gradient_preset('dark_slate')
|
self._apply_gradient_preset('dark_slate')
|
||||||
|
|
||||||
|
def action_migrate_adp_export_files(self):
|
||||||
|
"""Migrate ADP export files from Documents app to the new Export Files menu."""
|
||||||
|
return self.env['fusion_claims.adp.export.record'].migrate_from_documents()
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
|||||||
access_fusion_claims_config_user,fusion.central.config.user,model_fusion_claims_config,base.group_user,1,1,1,1
|
access_fusion_claims_config_user,fusion.central.config.user,model_fusion_claims_config,base.group_user,1,1,1,1
|
||||||
access_fusion_claims_export_wizard_user,fusion.central.export.wizard.user,model_fusion_claims_export_wizard,account.group_account_invoice,1,1,1,1
|
access_fusion_claims_export_wizard_user,fusion.central.export.wizard.user,model_fusion_claims_export_wizard,account.group_account_invoice,1,1,1,1
|
||||||
access_fusion_claims_export_wizard_manager,fusion.central.export.wizard.manager,model_fusion_claims_export_wizard,account.group_account_manager,1,1,1,1
|
access_fusion_claims_export_wizard_manager,fusion.central.export.wizard.manager,model_fusion_claims_export_wizard,account.group_account_manager,1,1,1,1
|
||||||
|
access_fusion_adp_export_record_user,fusion.adp.export.record.user,model_fusion_claims_adp_export_record,account.group_account_invoice,1,0,1,0
|
||||||
|
access_fusion_adp_export_record_manager,fusion.adp.export.record.manager,model_fusion_claims_adp_export_record,account.group_account_manager,1,1,1,1
|
||||||
access_fusion_adp_device_code_user,fusion.adp.device.code.user,model_fusion_adp_device_code,base.group_user,1,0,0,0
|
access_fusion_adp_device_code_user,fusion.adp.device.code.user,model_fusion_adp_device_code,base.group_user,1,0,0,0
|
||||||
access_fusion_adp_device_code_sales,fusion.adp.device.code.sales,model_fusion_adp_device_code,sales_team.group_sale_salesman,1,1,1,0
|
access_fusion_adp_device_code_sales,fusion.adp.device.code.sales,model_fusion_adp_device_code,sales_team.group_sale_salesman,1,1,1,0
|
||||||
access_fusion_adp_device_code_manager,fusion.adp.device.code.manager,model_fusion_adp_device_code,sales_team.group_sale_manager,1,1,1,1
|
access_fusion_adp_device_code_manager,fusion.adp.device.code.manager,model_fusion_adp_device_code,sales_team.group_sale_manager,1,1,1,1
|
||||||
|
|||||||
|
@@ -59,10 +59,11 @@
|
|||||||
<field name="inherit_id" ref="account.view_move_form"/>
|
<field name="inherit_id" ref="account.view_move_form"/>
|
||||||
<field name="priority">52</field>
|
<field name="priority">52</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<!-- Add Export and Sync buttons to header -->
|
<!-- Add Export and Verify buttons to header -->
|
||||||
<xpath expr="//button[@name='button_draft']" position="after">
|
<xpath expr="//button[@name='button_draft']" position="after">
|
||||||
<field name="x_fc_is_adp_invoice" invisible="1"/>
|
<field name="x_fc_is_adp_invoice" invisible="1"/>
|
||||||
<field name="x_fc_needs_device_verification" invisible="1"/>
|
<field name="x_fc_needs_device_verification" invisible="1"/>
|
||||||
|
<field name="x_fc_adp_invoice_portion" invisible="1"/>
|
||||||
<!-- Verify Device Approval button - only on client invoices that need verification -->
|
<!-- Verify Device Approval button - only on client invoices that need verification -->
|
||||||
<button name="action_open_device_approval_wizard"
|
<button name="action_open_device_approval_wizard"
|
||||||
string="Verify Device Approval"
|
string="Verify Device Approval"
|
||||||
@@ -71,19 +72,12 @@
|
|||||||
icon="fa-check-square-o"
|
icon="fa-check-square-o"
|
||||||
invisible="not x_fc_needs_device_verification"
|
invisible="not x_fc_needs_device_verification"
|
||||||
help="Complete device verification to enable ADP invoice creation"/>
|
help="Complete device verification to enable ADP invoice creation"/>
|
||||||
<button name="action_sync_to_sale_order"
|
|
||||||
string="Sync All"
|
|
||||||
type="object"
|
|
||||||
class="btn-secondary"
|
|
||||||
icon="fa-refresh"
|
|
||||||
invisible="move_type not in ['out_invoice', 'out_refund'] or not x_fc_is_adp_invoice"
|
|
||||||
help="Sync ADP fields from this Invoice to the Sale Order and all linked invoices"/>
|
|
||||||
<button name="action_export_adp_claim"
|
<button name="action_export_adp_claim"
|
||||||
string="Export ADP"
|
string="Export ADP"
|
||||||
type="object"
|
type="object"
|
||||||
class="btn-secondary"
|
class="btn-secondary"
|
||||||
icon="fa-file-text-o"
|
icon="fa-file-text-o"
|
||||||
invisible="move_type not in ['out_invoice', 'out_refund'] or state != 'posted' or not x_fc_is_adp_invoice"/>
|
invisible="move_type not in ['out_invoice', 'out_refund'] or state != 'posted' or not x_fc_is_adp_invoice or x_fc_adp_invoice_portion != 'adp'"/>
|
||||||
</xpath>
|
</xpath>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|||||||
@@ -1739,6 +1739,8 @@ else:
|
|||||||
action="action_adp_invoices" sequence="2"/>
|
action="action_adp_invoices" sequence="2"/>
|
||||||
<menuitem id="menu_adp_client_invoices" name="ADP Client Invoices" parent="menu_fc_adp"
|
<menuitem id="menu_adp_client_invoices" name="ADP Client Invoices" parent="menu_fc_adp"
|
||||||
action="action_adp_client_invoices" sequence="3"/>
|
action="action_adp_client_invoices" sequence="3"/>
|
||||||
|
<menuitem id="menu_adp_export_files" name="Export Files" parent="menu_fc_adp"
|
||||||
|
action="action_adp_export_records" sequence="4"/>
|
||||||
|
|
||||||
<menuitem id="menu_adp_quotations"
|
<menuitem id="menu_adp_quotations"
|
||||||
name="Quotation Stage"
|
name="Quotation Stage"
|
||||||
|
|||||||
142
fusion_claims/views/adp_export_record_views.xml
Normal file
142
fusion_claims/views/adp_export_record_views.xml
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
Copyright 2024-2025 Nexa Systems Inc.
|
||||||
|
License OPL-1 (Odoo Proprietary License v1.0)
|
||||||
|
Part of the Fusion Claim Assistant product family.
|
||||||
|
-->
|
||||||
|
<odoo>
|
||||||
|
<!-- ===================================================================== -->
|
||||||
|
<!-- ADP EXPORT RECORD: Tree View -->
|
||||||
|
<!-- ===================================================================== -->
|
||||||
|
<record id="view_adp_export_record_list" model="ir.ui.view">
|
||||||
|
<field name="name">fusion_claims.adp.export.record.list</field>
|
||||||
|
<field name="model">fusion_claims.adp.export.record</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<list string="ADP Export Files" default_order="export_date desc">
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="export_date"/>
|
||||||
|
<field name="posting_period_label" string="Posting Period"/>
|
||||||
|
<field name="vendor_code"/>
|
||||||
|
<field name="line_count" string="Lines"/>
|
||||||
|
<field name="invoice_count" string="Invoices"/>
|
||||||
|
<field name="user_id" string="Exported By" widget="many2one_avatar_user"/>
|
||||||
|
<field name="year" column_invisible="True"/>
|
||||||
|
<field name="month" column_invisible="True"/>
|
||||||
|
<field name="month_number" column_invisible="True"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- ===================================================================== -->
|
||||||
|
<!-- ADP EXPORT RECORD: Form View -->
|
||||||
|
<!-- ===================================================================== -->
|
||||||
|
<record id="view_adp_export_record_form" model="ir.ui.view">
|
||||||
|
<field name="name">fusion_claims.adp.export.record.form</field>
|
||||||
|
<field name="model">fusion_claims.adp.export.record</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="ADP Export File">
|
||||||
|
<header>
|
||||||
|
<button name="action_download" string="Download File"
|
||||||
|
type="object" class="btn-primary" icon="fa-download"/>
|
||||||
|
</header>
|
||||||
|
<sheet>
|
||||||
|
<div class="oe_title">
|
||||||
|
<h1><field name="name" readonly="1"/></h1>
|
||||||
|
</div>
|
||||||
|
<group>
|
||||||
|
<group string="Export Details">
|
||||||
|
<field name="export_date" readonly="1"/>
|
||||||
|
<field name="posting_period_date" readonly="1"/>
|
||||||
|
<field name="posting_period_label" readonly="1"/>
|
||||||
|
<field name="vendor_code" readonly="1"/>
|
||||||
|
<field name="line_count" readonly="1"/>
|
||||||
|
</group>
|
||||||
|
<group string="File">
|
||||||
|
<field name="file_data" filename="filename" readonly="1"/>
|
||||||
|
<field name="filename" invisible="1"/>
|
||||||
|
<field name="user_id" readonly="1"/>
|
||||||
|
<field name="company_id" readonly="1" groups="base.group_multi_company"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
<notebook>
|
||||||
|
<page string="Exported Invoices" name="invoices">
|
||||||
|
<field name="invoice_ids" readonly="1" nolabel="1">
|
||||||
|
<list string="Invoices" create="0" delete="0">
|
||||||
|
<field name="name" string="Invoice"/>
|
||||||
|
<field name="partner_id" string="Customer"/>
|
||||||
|
<field name="invoice_date"/>
|
||||||
|
<field name="amount_total" string="Total"/>
|
||||||
|
<field name="state" widget="badge"
|
||||||
|
decoration-success="state == 'posted'"
|
||||||
|
decoration-info="state == 'draft'"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</page>
|
||||||
|
<page string="Notes" name="notes" invisible="not notes">
|
||||||
|
<field name="notes" readonly="1"/>
|
||||||
|
</page>
|
||||||
|
</notebook>
|
||||||
|
</sheet>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- ===================================================================== -->
|
||||||
|
<!-- ADP EXPORT RECORD: Search View -->
|
||||||
|
<!-- ===================================================================== -->
|
||||||
|
<record id="view_adp_export_record_search" model="ir.ui.view">
|
||||||
|
<field name="name">fusion_claims.adp.export.record.search</field>
|
||||||
|
<field name="model">fusion_claims.adp.export.record</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<search string="ADP Export Files">
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="vendor_code"/>
|
||||||
|
<field name="user_id"/>
|
||||||
|
<separator/>
|
||||||
|
<group expand="1" string="Group By">
|
||||||
|
<filter string="Year" name="group_year"
|
||||||
|
context="{'group_by': 'year'}"/>
|
||||||
|
<filter string="Month" name="group_month"
|
||||||
|
context="{'group_by': 'month'}"/>
|
||||||
|
<filter string="Posting Period" name="group_posting"
|
||||||
|
context="{'group_by': 'posting_period_date'}"/>
|
||||||
|
<filter string="Exported By" name="group_user"
|
||||||
|
context="{'group_by': 'user_id'}"/>
|
||||||
|
</group>
|
||||||
|
</search>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- ===================================================================== -->
|
||||||
|
<!-- ADP EXPORT RECORD: Action -->
|
||||||
|
<!-- ===================================================================== -->
|
||||||
|
<record id="action_adp_export_records" model="ir.actions.act_window">
|
||||||
|
<field name="name">Export Files</field>
|
||||||
|
<field name="res_model">fusion_claims.adp.export.record</field>
|
||||||
|
<field name="view_mode">list,form</field>
|
||||||
|
<field name="search_view_id" ref="view_adp_export_record_search"/>
|
||||||
|
<field name="context">{'search_default_group_year': 1, 'search_default_group_month': 1, 'search_default_group_posting': 1}</field>
|
||||||
|
<field name="help" type="html">
|
||||||
|
<p class="o_view_nocontent_smiling_face">
|
||||||
|
No export files yet
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
ADP export files will appear here after you export invoices using the
|
||||||
|
<strong>Export ADP</strong> button on ADP portion invoices.
|
||||||
|
</p>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- ===================================================================== -->
|
||||||
|
<!-- ADP EXPORT RECORD: Server Action for Bulk ZIP Download -->
|
||||||
|
<!-- ===================================================================== -->
|
||||||
|
<record id="action_adp_export_download_zip" model="ir.actions.server">
|
||||||
|
<field name="name">Download as ZIP</field>
|
||||||
|
<field name="model_id" ref="model_fusion_claims_adp_export_record"/>
|
||||||
|
<field name="binding_model_id" ref="model_fusion_claims_adp_export_record"/>
|
||||||
|
<field name="binding_view_types">list</field>
|
||||||
|
<field name="state">code</field>
|
||||||
|
<field name="code">action = records.action_download_zip()</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
@@ -330,6 +330,30 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- ============================================================= -->
|
||||||
|
<!-- DATA MIGRATION -->
|
||||||
|
<!-- ============================================================= -->
|
||||||
|
<h2>Data Migration</h2>
|
||||||
|
<div class="row mt-4 o_settings_container">
|
||||||
|
<div class="col-12 col-lg-6 o_setting_box">
|
||||||
|
<div class="o_setting_right_pane">
|
||||||
|
<span class="o_form_label">Migrate Export Files from Documents</span>
|
||||||
|
<div class="text-muted">
|
||||||
|
Move existing ADP export files from the Documents app into the
|
||||||
|
new ADP Export Files menu. Only needs to be run once after upgrading.
|
||||||
|
</div>
|
||||||
|
<div class="mt-2">
|
||||||
|
<button name="action_migrate_adp_export_files"
|
||||||
|
string="Migrate Export Files"
|
||||||
|
type="object"
|
||||||
|
class="btn-secondary"
|
||||||
|
icon="fa-exchange"
|
||||||
|
confirm="This will migrate all ADP export files from Documents to the new Export Files menu. Continue?"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- ============================================================= -->
|
<!-- ============================================================= -->
|
||||||
<!-- ODSP CONFIGURATION -->
|
<!-- ODSP CONFIGURATION -->
|
||||||
<!-- ============================================================= -->
|
<!-- ============================================================= -->
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ class FusionCentralExportWizard(models.TransientModel):
|
|||||||
('done', 'Done'),
|
('done', 'Done'),
|
||||||
], default='draft')
|
], default='draft')
|
||||||
export_summary = fields.Text(string='Export Summary', readonly=True)
|
export_summary = fields.Text(string='Export Summary', readonly=True)
|
||||||
saved_to_documents = fields.Boolean(string='Saved to Documents', readonly=True)
|
saved_to_export_records = fields.Boolean(string='Saved to Export Files', readonly=True)
|
||||||
warnings = fields.Text(string='Warnings', readonly=True)
|
warnings = fields.Text(string='Warnings', readonly=True)
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
@@ -343,37 +343,20 @@ class FusionCentralExportWizard(models.TransientModel):
|
|||||||
return f"{self.vendor_code}_{self.export_date.strftime('%Y-%m-%d')}.txt"
|
return f"{self.vendor_code}_{self.export_date.strftime('%Y-%m-%d')}.txt"
|
||||||
|
|
||||||
def _check_existing_file(self, filename):
|
def _check_existing_file(self, filename):
|
||||||
"""Check if a file with the same name already exists in Documents.
|
"""Check if a file with the same name already exists in ADP Export Records.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
tuple: (exists: bool, existing_files: list of names)
|
tuple: (exists: bool, existing_files: list of description strings)
|
||||||
"""
|
"""
|
||||||
existing_files = []
|
existing_files = []
|
||||||
|
ExportRecord = self.env['fusion_claims.adp.export.record']
|
||||||
if 'documents.document' not in self.env:
|
existing = ExportRecord.search([('name', '=', filename)])
|
||||||
return False, existing_files
|
if existing:
|
||||||
|
existing_files = [
|
||||||
try:
|
f"{rec.name} (exported: {rec.export_date.strftime('%Y-%m-%d %H:%M')})"
|
||||||
# Get the folder where we save files
|
for rec in existing
|
||||||
folder = self._get_or_create_documents_folder()
|
]
|
||||||
if not folder:
|
return True, existing_files
|
||||||
return False, existing_files
|
|
||||||
|
|
||||||
# Search for files with the same name in our folder
|
|
||||||
existing = self.env['documents.document'].search([
|
|
||||||
('name', '=', filename),
|
|
||||||
('type', '=', 'binary'),
|
|
||||||
('folder_id', '=', folder.id),
|
|
||||||
])
|
|
||||||
|
|
||||||
if existing:
|
|
||||||
existing_files = [f"{doc.name} (created: {doc.create_date.strftime('%Y-%m-%d %H:%M')})"
|
|
||||||
for doc in existing]
|
|
||||||
return True, existing_files
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
_logger.warning("Error checking existing files: %s", str(e))
|
|
||||||
|
|
||||||
return False, existing_files
|
return False, existing_files
|
||||||
|
|
||||||
def action_export(self):
|
def action_export(self):
|
||||||
@@ -383,8 +366,8 @@ class FusionCentralExportWizard(models.TransientModel):
|
|||||||
1. Validate inputs
|
1. Validate inputs
|
||||||
2. Generate content (includes verification - may raise UserError)
|
2. Generate content (includes verification - may raise UserError)
|
||||||
3. Check for existing files (warn but don't block)
|
3. Check for existing files (warn but don't block)
|
||||||
4. Show download window
|
4. Save to ADP Export Records (filestore-backed)
|
||||||
5. Save to Documents ONLY after successful generation
|
5. Show download window
|
||||||
"""
|
"""
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
|
|
||||||
@@ -394,35 +377,26 @@ class FusionCentralExportWizard(models.TransientModel):
|
|||||||
if not self.vendor_code:
|
if not self.vendor_code:
|
||||||
raise UserError(_("Please enter a vendor code."))
|
raise UserError(_("Please enter a vendor code."))
|
||||||
|
|
||||||
# Generate filename first
|
|
||||||
filename = self._get_export_filename()
|
filename = self._get_export_filename()
|
||||||
|
|
||||||
# Check for existing file with same name BEFORE generating content
|
|
||||||
file_exists, existing_files = self._check_existing_file(filename)
|
file_exists, existing_files = self._check_existing_file(filename)
|
||||||
|
|
||||||
# Generate content - this includes all validation and verification
|
|
||||||
# If verification fails, UserError is raised and we don't save anything
|
|
||||||
content, line_count, warnings = self._generate_export_content()
|
content, line_count, warnings = self._generate_export_content()
|
||||||
|
|
||||||
# If we got here, content generation was successful (no errors)
|
|
||||||
file_data = base64.b64encode(content.encode('utf-8'))
|
file_data = base64.b64encode(content.encode('utf-8'))
|
||||||
|
|
||||||
# Add warning if file already exists
|
|
||||||
if file_exists:
|
if file_exists:
|
||||||
warnings.append(
|
warnings.append(
|
||||||
f"WARNING: A file with the name '{filename}' already exists in Documents.\n"
|
f"WARNING: A file with the name '{filename}' already exists in Export Files.\n"
|
||||||
f"Existing files: {', '.join(existing_files)}\n"
|
f"Existing files: {', '.join(existing_files)}\n"
|
||||||
f"ADP does not accept renamed files. You will need to manually rename "
|
f"ADP does not accept renamed files. You will need to manually rename "
|
||||||
f"before submitting if this is a resubmission."
|
f"before submitting if this is a resubmission."
|
||||||
)
|
)
|
||||||
|
|
||||||
# Build warnings text BEFORE saving (so user sees the warning)
|
|
||||||
warnings_text = '\n'.join(warnings) if warnings else ''
|
warnings_text = '\n'.join(warnings) if warnings else ''
|
||||||
|
|
||||||
# Now save to Documents (only after successful generation)
|
saved = self._save_to_export_records(filename, file_data, line_count)
|
||||||
saved = self._save_to_documents(filename, content)
|
|
||||||
|
|
||||||
# Update invoices as exported
|
|
||||||
for invoice in self.invoice_ids:
|
for invoice in self.invoice_ids:
|
||||||
invoice.write({
|
invoice.write({
|
||||||
'adp_exported': True,
|
'adp_exported': True,
|
||||||
@@ -430,20 +404,16 @@ class FusionCentralExportWizard(models.TransientModel):
|
|||||||
'adp_export_count': invoice.adp_export_count + 1,
|
'adp_export_count': invoice.adp_export_count + 1,
|
||||||
})
|
})
|
||||||
|
|
||||||
# Build summary
|
|
||||||
summary = _("Exported %d lines from %d invoices.") % (line_count, len(self.invoice_ids))
|
summary = _("Exported %d lines from %d invoices.") % (line_count, len(self.invoice_ids))
|
||||||
if saved:
|
if saved:
|
||||||
summary += "\n" + _("File saved to Documents: ADP Billing Files/%s/%s/") % (
|
summary += "\n" + _("File saved to Fusion Claims > ADP > Export Files.")
|
||||||
date.today().year, date.today().strftime('%B')
|
|
||||||
)
|
|
||||||
|
|
||||||
# Update wizard with results
|
|
||||||
self.write({
|
self.write({
|
||||||
'export_file': file_data,
|
'export_file': file_data,
|
||||||
'export_filename': filename,
|
'export_filename': filename,
|
||||||
'state': 'done',
|
'state': 'done',
|
||||||
'export_summary': summary,
|
'export_summary': summary,
|
||||||
'saved_to_documents': saved,
|
'saved_to_export_records': saved,
|
||||||
'warnings': warnings_text,
|
'warnings': warnings_text,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -455,163 +425,28 @@ class FusionCentralExportWizard(models.TransientModel):
|
|||||||
'target': 'new',
|
'target': 'new',
|
||||||
}
|
}
|
||||||
|
|
||||||
def _save_to_documents(self, filename, content):
|
def _save_to_export_records(self, filename, file_data, line_count):
|
||||||
"""Save export file to Documents app if available."""
|
"""Save export file to the ADP Export Records model (filestore-backed)."""
|
||||||
if 'documents.document' not in self.env:
|
|
||||||
return False
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
Documents = self.env['documents.document']
|
ExportRecord = self.env['fusion_claims.adp.export.record']
|
||||||
|
posting_date = ExportRecord._get_current_posting_date(self.export_date)
|
||||||
|
|
||||||
# Get or create folder structure (in Company workspace)
|
ExportRecord.create({
|
||||||
folder = self._get_or_create_documents_folder()
|
|
||||||
if not folder:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Create document in the Company workspace folder
|
|
||||||
# Don't set company_id or owner_id - inherits from folder (Company workspace)
|
|
||||||
Documents.sudo().create({
|
|
||||||
'name': filename,
|
'name': filename,
|
||||||
'type': 'binary',
|
'filename': filename,
|
||||||
'datas': base64.b64encode(content.encode('utf-8')),
|
'file_data': file_data,
|
||||||
'folder_id': folder.id,
|
'export_date': fields.Datetime.now(),
|
||||||
'access_internal': 'edit', # Allow internal users to access
|
'posting_period_date': posting_date,
|
||||||
|
'vendor_code': self.vendor_code,
|
||||||
|
'line_count': line_count,
|
||||||
|
'invoice_ids': [(6, 0, self.invoice_ids.ids)],
|
||||||
|
'user_id': self.env.uid,
|
||||||
|
'company_id': self.env.company.id,
|
||||||
})
|
})
|
||||||
|
|
||||||
_logger.info("Saved ADP export to Documents: %s", filename)
|
_logger.info("Saved ADP export record: %s", filename)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
_logger.warning("Could not save to Documents: %s", str(e))
|
_logger.error("Could not save ADP export record: %s", str(e))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _get_or_create_documents_folder(self):
|
|
||||||
"""Get or create the ADP Billing Files folder structure."""
|
|
||||||
if 'documents.document' not in self.env:
|
|
||||||
return False
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Odoo 18/19 stores folders as documents.document with type='folder'
|
|
||||||
# The documents.folder model doesn't exist in newer versions
|
|
||||||
return self._get_or_create_folder_v18()
|
|
||||||
except Exception as e:
|
|
||||||
_logger.warning("Could not create folder: %s", str(e))
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _get_or_create_folder_v18(self):
|
|
||||||
"""Get or create folders for Odoo 18+.
|
|
||||||
|
|
||||||
In Odoo 18/19, folders are stored as documents.document records with type='folder'.
|
|
||||||
To make folders appear in Company workspace (not My Drive), we need:
|
|
||||||
- company_id = False (not set)
|
|
||||||
- owner_id = False (not set)
|
|
||||||
- access_internal = 'edit' (allows internal users to access)
|
|
||||||
"""
|
|
||||||
Document = self.env['documents.document']
|
|
||||||
today = date.today()
|
|
||||||
|
|
||||||
# Root folder: ADP Billing Files (in Company workspace)
|
|
||||||
root_folder = Document.search([
|
|
||||||
('name', 'ilike', 'ADP Billing Files'),
|
|
||||||
('type', '=', 'folder'),
|
|
||||||
('folder_id', '=', False), # Root level folder
|
|
||||||
], limit=1)
|
|
||||||
|
|
||||||
if not root_folder:
|
|
||||||
root_folder = Document.sudo().create({
|
|
||||||
'name': 'ADP Billing Files',
|
|
||||||
'type': 'folder',
|
|
||||||
'access_internal': 'edit', # Company workspace access
|
|
||||||
'access_via_link': 'none',
|
|
||||||
# Don't set company_id or owner_id - makes it a Company folder
|
|
||||||
})
|
|
||||||
|
|
||||||
# Year folder
|
|
||||||
year_name = str(today.year)
|
|
||||||
year_folder = Document.search([
|
|
||||||
('name', 'ilike', year_name),
|
|
||||||
('type', '=', 'folder'),
|
|
||||||
('folder_id', '=', root_folder.id),
|
|
||||||
], limit=1)
|
|
||||||
|
|
||||||
if not year_folder:
|
|
||||||
year_folder = Document.sudo().create({
|
|
||||||
'name': year_name,
|
|
||||||
'type': 'folder',
|
|
||||||
'folder_id': root_folder.id,
|
|
||||||
'access_internal': 'edit',
|
|
||||||
'access_via_link': 'none',
|
|
||||||
})
|
|
||||||
|
|
||||||
# Month folder
|
|
||||||
month_name = today.strftime('%B')
|
|
||||||
month_folder = Document.search([
|
|
||||||
('name', 'ilike', month_name),
|
|
||||||
('type', '=', 'folder'),
|
|
||||||
('folder_id', '=', year_folder.id),
|
|
||||||
], limit=1)
|
|
||||||
|
|
||||||
if not month_folder:
|
|
||||||
month_folder = Document.sudo().create({
|
|
||||||
'name': month_name,
|
|
||||||
'type': 'folder',
|
|
||||||
'folder_id': year_folder.id,
|
|
||||||
'access_internal': 'edit',
|
|
||||||
'access_via_link': 'none',
|
|
||||||
})
|
|
||||||
|
|
||||||
return month_folder
|
|
||||||
|
|
||||||
def _get_or_create_folder_legacy(self):
|
|
||||||
"""Get or create folders for older Odoo versions."""
|
|
||||||
Documents = self.env['documents.document']
|
|
||||||
company = self.env.company
|
|
||||||
today = date.today()
|
|
||||||
|
|
||||||
# Root folder
|
|
||||||
root_folder = Documents.search([
|
|
||||||
('name', '=', 'ADP Billing Files'),
|
|
||||||
('type', '=', 'folder'),
|
|
||||||
('company_id', '=', company.id),
|
|
||||||
], limit=1)
|
|
||||||
|
|
||||||
if not root_folder:
|
|
||||||
root_folder = Documents.create({
|
|
||||||
'name': 'ADP Billing Files',
|
|
||||||
'type': 'folder',
|
|
||||||
'company_id': company.id,
|
|
||||||
})
|
|
||||||
|
|
||||||
# Year folder
|
|
||||||
year_name = str(today.year)
|
|
||||||
year_folder = Documents.search([
|
|
||||||
('name', '=', year_name),
|
|
||||||
('type', '=', 'folder'),
|
|
||||||
('folder_id', '=', root_folder.id),
|
|
||||||
], limit=1)
|
|
||||||
|
|
||||||
if not year_folder:
|
|
||||||
year_folder = Documents.create({
|
|
||||||
'name': year_name,
|
|
||||||
'type': 'folder',
|
|
||||||
'folder_id': root_folder.id,
|
|
||||||
'company_id': company.id,
|
|
||||||
})
|
|
||||||
|
|
||||||
# Month folder
|
|
||||||
month_name = today.strftime('%B')
|
|
||||||
month_folder = Documents.search([
|
|
||||||
('name', '=', month_name),
|
|
||||||
('type', '=', 'folder'),
|
|
||||||
('folder_id', '=', year_folder.id),
|
|
||||||
], limit=1)
|
|
||||||
|
|
||||||
if not month_folder:
|
|
||||||
month_folder = Documents.create({
|
|
||||||
'name': month_name,
|
|
||||||
'type': 'folder',
|
|
||||||
'folder_id': year_folder.id,
|
|
||||||
'company_id': company.id,
|
|
||||||
})
|
|
||||||
|
|
||||||
return month_folder
|
|
||||||
|
|||||||
@@ -40,13 +40,13 @@
|
|||||||
<field name="export_file" filename="export_filename" readonly="1"/>
|
<field name="export_file" filename="export_filename" readonly="1"/>
|
||||||
</group>
|
</group>
|
||||||
|
|
||||||
<div class="text-muted small" invisible="not saved_to_documents">
|
<div class="text-muted small" invisible="not saved_to_export_records">
|
||||||
<i class="fa fa-folder-open"/> File also saved to Documents app
|
<i class="fa fa-folder-open"/> File saved to Fusion Claims > ADP > Export Files
|
||||||
</div>
|
</div>
|
||||||
</group>
|
</group>
|
||||||
|
|
||||||
<field name="state" invisible="1"/>
|
<field name="state" invisible="1"/>
|
||||||
<field name="saved_to_documents" invisible="1"/>
|
<field name="saved_to_export_records" invisible="1"/>
|
||||||
<field name="warnings" invisible="1"/>
|
<field name="warnings" invisible="1"/>
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
|
|||||||
Reference in New Issue
Block a user