New module commercial_invoice for preparing and printing commercial invoices.
This commit is contained in:
parent
30afde78cc
commit
0644586a52
17 changed files with 638 additions and 0 deletions
4
commercial_invoice/__init__.py
Normal file
4
commercial_invoice/__init__.py
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
from . import report
|
||||
from . import models
|
||||
32
commercial_invoice/__manifest__.py
Normal file
32
commercial_invoice/__manifest__.py
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
'name': 'Commercial Invoice',
|
||||
'version': '1.0',
|
||||
'category': 'Accounting',
|
||||
'summary': 'Generate commercial invoices for cross-border shipments',
|
||||
'description': """
|
||||
Generate commercial invoices for cross-border shipments between Canada and the USA.
|
||||
Features:
|
||||
- Group multiple invoices into a single commercial invoice
|
||||
- Track additional costs (packaging, freight, insurance)
|
||||
- Print bilingual commercial invoice reports
|
||||
""",
|
||||
'author': 'marc@bemade.org',
|
||||
'website': 'https://www.bemade.org',
|
||||
'license': 'LGPL-3',
|
||||
'depends': [
|
||||
'account',
|
||||
'stock_delivery',
|
||||
'mail',
|
||||
],
|
||||
'data': [
|
||||
'security/ir.model.access.csv',
|
||||
'data/commercial_invoice_sequence.xml',
|
||||
'report/commercial_invoice_report.xml',
|
||||
'report/report_templates.xml',
|
||||
'views/account_move_views.xml',
|
||||
'views/commercial_invoice_views.xml',
|
||||
],
|
||||
'installable': True,
|
||||
'application': False,
|
||||
'auto_install': False,
|
||||
}
|
||||
12
commercial_invoice/data/commercial_invoice_sequence.xml
Normal file
12
commercial_invoice/data/commercial_invoice_sequence.xml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<record id="seq_commercial_invoice" model="ir.sequence">
|
||||
<field name="name">Commercial Invoice</field>
|
||||
<field name="code">commercial.invoice</field>
|
||||
<field name="prefix">CI/%(year)s/</field>
|
||||
<field name="padding">4</field>
|
||||
<field name="company_id" eval="False"/>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
2
commercial_invoice/models/__init__.py
Normal file
2
commercial_invoice/models/__init__.py
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
from . import commercial_invoice
|
||||
from . import account_move
|
||||
15
commercial_invoice/models/account_move.py
Normal file
15
commercial_invoice/models/account_move.py
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
from odoo import models
|
||||
|
||||
class AccountMove(models.Model):
|
||||
_inherit = 'account.move'
|
||||
|
||||
def action_create_commercial_invoice(self):
|
||||
"""Create a commercial invoice from selected invoices."""
|
||||
commercial_invoice = self.env['commercial.invoice'].create_from_invoices(self)
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'commercial.invoice',
|
||||
'res_id': commercial_invoice.id,
|
||||
'view_mode': 'form',
|
||||
'target': 'current',
|
||||
}
|
||||
114
commercial_invoice/models/commercial_invoice.py
Normal file
114
commercial_invoice/models/commercial_invoice.py
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
class CommercialInvoice(models.Model):
|
||||
_name = 'commercial.invoice'
|
||||
_description = 'Commercial Invoice for Export'
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||
_order = 'date desc, id desc'
|
||||
|
||||
name = fields.Char(string='Reference', required=True, copy=False, readonly=True, default='New')
|
||||
date = fields.Date(string='Export Date', required=True, default=fields.Date.context_today)
|
||||
state = fields.Selection([
|
||||
('draft', 'Draft'),
|
||||
('done', 'Done'),
|
||||
('cancelled', 'Cancelled')
|
||||
], string='Status', default='draft', tracking=True)
|
||||
|
||||
# Related parties
|
||||
partner_id = fields.Many2one('res.partner', string='Consignee', required=True)
|
||||
importer_id = fields.Many2one('res.partner', string='Importer of Record')
|
||||
customs_broker_id = fields.Many2one('res.partner', string='Customs Broker')
|
||||
company_id = fields.Many2one('res.company', string='Company', required=True, default=lambda self: self.env.company)
|
||||
currency_id = fields.Many2one('res.currency', string='Currency', required=True,
|
||||
default=lambda self: self.env.company.currency_id)
|
||||
related_parties = fields.Boolean(string='Related Parties', default=False)
|
||||
|
||||
|
||||
# Invoice lines and related fields
|
||||
invoice_ids = fields.Many2many('account.move', string='Invoices', domain=[('move_type', '=', 'out_invoice')])
|
||||
payment_term_id = fields.Many2one('account.payment.term', string='Payment Terms')
|
||||
incoterm_id = fields.Many2one('account.incoterms', string='Incoterms')
|
||||
|
||||
# Shipping details
|
||||
number_of_packages = fields.Integer(string='Number of Packages')
|
||||
total_weight = fields.Float(string='Total Weight (kg)')
|
||||
packaging_cost = fields.Monetary(string='Packaging Cost', currency_field='currency_id')
|
||||
freight_cost = fields.Monetary(string='Freight Cost', currency_field='currency_id')
|
||||
insurance_cost = fields.Monetary(string='Insurance Cost', currency_field='currency_id')
|
||||
other_cost = fields.Monetary(string='Other Costs', currency_field='currency_id')
|
||||
|
||||
# Computed fields
|
||||
invoice_amount = fields.Monetary(string='Invoice Amount', currency_field='currency_id',
|
||||
compute='_compute_amounts', store=True)
|
||||
total_amount = fields.Monetary(string='Total Amount', currency_field='currency_id',
|
||||
compute='_compute_amounts', store=True)
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
for vals in vals_list:
|
||||
if vals.get('name', 'New') == 'New':
|
||||
vals['name'] = self.env['ir.sequence'].next_by_code('commercial.invoice') or 'New'
|
||||
return super().create(vals_list)
|
||||
|
||||
@api.depends('invoice_ids', 'packaging_cost', 'freight_cost', 'insurance_cost', 'other_cost')
|
||||
def _compute_amounts(self):
|
||||
for record in self:
|
||||
record.invoice_amount = sum(record.invoice_ids.mapped('amount_total'))
|
||||
record.total_amount = (record.invoice_amount + record.packaging_cost +
|
||||
record.freight_cost + record.insurance_cost + record.other_cost)
|
||||
|
||||
def action_confirm(self):
|
||||
self.write({'state': 'done'})
|
||||
|
||||
def action_draft(self):
|
||||
self.write({'state': 'draft'})
|
||||
|
||||
def action_cancel(self):
|
||||
self.write({'state': 'cancelled'})
|
||||
|
||||
@api.onchange('partner_id')
|
||||
def _onchange_partner_id(self):
|
||||
if self.partner_id:
|
||||
self.payment_term_id = self.partner_id.property_payment_term_id
|
||||
|
||||
@api.model
|
||||
def _prepare_commercial_invoice_from_invoices(self, invoices):
|
||||
"""Prepare commercial invoice values from a set of invoices."""
|
||||
if not invoices:
|
||||
raise UserError(_("No invoices selected."))
|
||||
|
||||
# Get unique values for key fields
|
||||
currencies = invoices.mapped('currency_id')
|
||||
payment_terms = invoices.mapped('invoice_payment_term_id')
|
||||
incoterms = invoices.mapped('invoice_incoterm_id')
|
||||
companies = invoices.mapped('company_id')
|
||||
|
||||
# Validate consistency
|
||||
if len(currencies) > 1:
|
||||
raise UserError(_("Selected invoices have different currencies."))
|
||||
if len(companies) > 1:
|
||||
raise UserError(_("Selected invoices are from different companies."))
|
||||
|
||||
# Get shipping and billing partners
|
||||
shipping_partners = invoices.mapped('partner_shipping_id.commercial_partner_id')
|
||||
billing_partners = invoices.mapped('partner_id.commercial_partner_id')
|
||||
|
||||
# Prepare values
|
||||
vals = {
|
||||
'invoice_ids': [(6, 0, invoices.ids)],
|
||||
'company_id': companies[0].id,
|
||||
'currency_id': currencies[0].id,
|
||||
'partner_id': shipping_partners[0].id if len(shipping_partners) == 1 else False,
|
||||
'importer_id': billing_partners[0].id if len(billing_partners) == 1 else False,
|
||||
'payment_term_id': payment_terms[0].id if len(payment_terms) == 1 else False,
|
||||
'incoterm_id': incoterms[0].id if len(incoterms) == 1 else False,
|
||||
}
|
||||
|
||||
return vals
|
||||
|
||||
@api.model
|
||||
def create_from_invoices(self, invoices):
|
||||
"""Create a commercial invoice from a set of invoices."""
|
||||
vals = self._prepare_commercial_invoice_from_invoices(invoices)
|
||||
return self.create(vals)
|
||||
2
commercial_invoice/report/__init__.py
Normal file
2
commercial_invoice/report/__init__.py
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
13
commercial_invoice/report/commercial_invoice_report.xml
Normal file
13
commercial_invoice/report/commercial_invoice_report.xml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="action_report_commercial_invoice" model="ir.actions.report">
|
||||
<field name="name">Commercial Invoice</field>
|
||||
<field name="model">commercial.invoice</field>
|
||||
<field name="report_type">qweb-pdf</field>
|
||||
<field name="report_name">commercial_invoice.report_commercial_invoice</field>
|
||||
<field name="report_file">commercial_invoice.report_commercial_invoice</field>
|
||||
<field name="print_report_name">'Commercial Invoice - %s' % object.name</field>
|
||||
<field name="binding_model_id" ref="model_commercial_invoice"/>
|
||||
<field name="binding_type">report</field>
|
||||
</record>
|
||||
</odoo>
|
||||
219
commercial_invoice/report/report_templates.xml
Normal file
219
commercial_invoice/report/report_templates.xml
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- Main template that will be called by the action -->
|
||||
<template id="report_commercial_invoice">
|
||||
<t t-call="web.html_container">
|
||||
<t t-foreach="docs" t-as="o">
|
||||
<t t-call="commercial_invoice.report_commercial_invoice_document"/>
|
||||
</t>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<!-- Document template with the actual content -->
|
||||
<template id="report_commercial_invoice_document">
|
||||
<t t-call="web.external_layout">
|
||||
<div class="page">
|
||||
<h2 class="text-center">Commercial Invoice / Facture Commerciale</h2>
|
||||
|
||||
<!-- Header Info -->
|
||||
<table class="table table-sm table-bordered mt-4">
|
||||
<tr>
|
||||
<td width="33%">
|
||||
<strong>Export Date / Date d'exportation:</strong><br/>
|
||||
<span t-field="o.date"/>
|
||||
</td>
|
||||
<td width="33%">
|
||||
<strong>Invoice # / No. de facture:</strong><br/>
|
||||
<span t-field="o.name"/>
|
||||
</td>
|
||||
<td width="33%">
|
||||
<strong>Related Parties / Parties liées:</strong><br/>
|
||||
<span t-out="'Yes / Oui' if o.related_parties else 'No / Non'"></span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- Shipper/Consignee -->
|
||||
<table class="table table-sm table-bordered">
|
||||
<tr>
|
||||
<td width="50%">
|
||||
<strong>Shipper/Exporter - Expéditeur/Exportateur:</strong><br/>
|
||||
<div t-field="o.company_id.partner_id" t-options='{"widget": "contact", "fields": ["name", "address"], "no_marker": True}'/>
|
||||
<div t-if="o.company_id.vat">Tax ID: <span t-field="o.company_id.vat"/></div>
|
||||
</td>
|
||||
<td width="50%">
|
||||
<strong>Consignee - Destinataire:</strong><br/>
|
||||
<div t-field="o.partner_id" t-options='{"widget": "contact", "fields": ["name", "address"], "no_marker": True}'/>
|
||||
<div t-if="o.partner_id.vat">Tax ID: <span t-field="o.partner_id.vat"/></div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- Export/Terms/Currency -->
|
||||
<table class="table table-sm table-bordered">
|
||||
<tr>
|
||||
<td width="33%">
|
||||
<strong>Country of Export / Pays d'exportation:</strong><br/>
|
||||
<span t-field="o.company_id.country_id.name"/>
|
||||
</td>
|
||||
<td width="33%">
|
||||
<strong>Terms of Sale / Conditions de vente:</strong><br/>
|
||||
<span t-field="o.payment_term_id.name"/>
|
||||
</td>
|
||||
<td width="33%">
|
||||
<strong>Currency / Devise:</strong><br/>
|
||||
<span t-field="o.currency_id.name"/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- Origin/Importer -->
|
||||
<table class="table table-sm table-bordered">
|
||||
<tr>
|
||||
<td width="50%">
|
||||
<strong>Country of Origin / Pays d'origine:</strong><br/>
|
||||
<t t-set="all_lines" t-value="o.invoice_ids.mapped('invoice_line_ids')"/>
|
||||
<t t-set="origins" t-value="all_lines.mapped('product_id.country_of_origin.name')"/>
|
||||
<span t-out="', '.join([x for x in origins if x])"/>
|
||||
</td>
|
||||
<td width="50%" rowspan="2">
|
||||
<strong>Importer of Record / Importateur attitré:</strong><br/>
|
||||
<span>(if different than consignee / si différent du destinataire)</span><br/>
|
||||
<t t-if="o.importer_id != o.partner_id">
|
||||
<div t-field="o.importer_id" t-options='{"widget": "contact", "fields": ["name", "address"], "no_marker": True}'/>
|
||||
</t>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<strong>Country of Ultimate Destination / Pays de destination finale:</strong><br/>
|
||||
<span t-field="o.partner_id.country_id.name"/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- Line Items -->
|
||||
<table class="table table-sm table-bordered mt-4">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Item # & Description<br/>No. d'article & description</th>
|
||||
<th>Client Item #<br/>No. d'article client</th>
|
||||
<th>HS #<br/>No. SH</th>
|
||||
<th>Quantity<br/>Quantité</th>
|
||||
<th>Unit Value<br/>Valeur unitaire</th>
|
||||
<th>Total Value<br/>Valeur totale</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<t t-set="all_lines" t-value="o.invoice_ids.mapped('invoice_line_ids')"/>
|
||||
<tr t-foreach="all_lines" t-as="line">
|
||||
<td>
|
||||
<span t-field="line.product_id.name"/>
|
||||
<t t-if="line.name != line.product_id.name">
|
||||
<br/><span class="text-muted" t-field="line.name"/>
|
||||
</t>
|
||||
</td>
|
||||
<td><span t-field="line.product_id.default_code"/></td>
|
||||
<td><span t-field="line.product_id.hs_code"/></td>
|
||||
<td>
|
||||
<span t-field="line.quantity"/>
|
||||
<span t-field="line.product_uom_id" groups="uom.group_uom"/>
|
||||
</td>
|
||||
<td>
|
||||
<span t-field="line.price_unit" t-options='{"widget": "monetary", "display_currency": o.currency_id}'/>
|
||||
</td>
|
||||
<td>
|
||||
<span t-field="line.price_subtotal" t-options='{"widget": "monetary", "display_currency": o.currency_id}'/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- Costs and Totals -->
|
||||
<div class="row">
|
||||
<div class="col-7">
|
||||
<!-- Declaration -->
|
||||
<p class="small">
|
||||
These commodities, technology or software were exported from Canada in accordance with the Export
|
||||
Administration Regulations. Diversion contrary to Canadian law prohibited.<br/>
|
||||
It is hereby certified that this invoice shows the actual price of the goods described, that no other invoice has
|
||||
been issued and that all particulars are true and correct.<br/>
|
||||
----------------------------------------------------------------------------<br/>
|
||||
Ces marchandises, technologies ou logiciels ont été exportés du Canada conformément aux règlements
|
||||
administratifs sur l'exportation des Etats-Unis. Tout agissement contraire à la loi Canadienne est strictement
|
||||
interdit.<br/>
|
||||
Je certifie par la présente que les prix indiqués sur cette facture sont exacts, qu'aucune autre facture
|
||||
commerciale n'a été produite et que tous les renseignements fournis sont véridiques.
|
||||
</p>
|
||||
<!-- Signature -->
|
||||
<div class="mt-4">
|
||||
<div>Signature: _______________________</div>
|
||||
<div>Title / Titre: _______________________</div>
|
||||
<div>Date: _______________________</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-5">
|
||||
<table class="table table-sm">
|
||||
<tr>
|
||||
<td><strong>Packaging / Emballage:</strong></td>
|
||||
<td class="text-right">
|
||||
<span t-field="o.packaging_cost" t-options='{"widget": "monetary", "display_currency": o.currency_id}'/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Freight / Fret:</strong></td>
|
||||
<td class="text-right">
|
||||
<span t-field="o.freight_cost" t-options='{"widget": "monetary", "display_currency": o.currency_id}'/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Insurance / Assurance:</strong></td>
|
||||
<td class="text-right">
|
||||
<span t-field="o.insurance_cost" t-options='{"widget": "monetary", "display_currency": o.currency_id}'/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Other / Autre:</strong></td>
|
||||
<td class="text-right">
|
||||
<span t-field="o.other_cost" t-options='{"widget": "monetary", "display_currency": o.currency_id}'/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="border-black">
|
||||
<td><strong>Total Invoice Value / Valeur totale de la facture:</strong></td>
|
||||
<td class="text-right">
|
||||
<strong t-field="o.total_amount" t-options='{"widget": "monetary", "display_currency": o.currency_id}'/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer Info -->
|
||||
<table class="table table-sm table-bordered mt-4">
|
||||
<tr>
|
||||
<td width="33%">
|
||||
<strong>Customs Broker / Courtier en douane:</strong><br/>
|
||||
<span t-field="o.customs_broker_id.name"/>
|
||||
</td>
|
||||
<td width="33%">
|
||||
<strong>Number of Packages / Nombre de colis:</strong><br/>
|
||||
<span t-field="o.number_of_packages"/>
|
||||
</td>
|
||||
<td width="33%">
|
||||
<strong>Total Weight / Poids total:</strong><br/>
|
||||
<span t-field="o.total_weight"/> kg
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="3">
|
||||
<strong>Incoterms:</strong>
|
||||
<t t-if="o.incoterm_id">
|
||||
<span t-field="o.incoterm_id.code"/> - <span t-field="o.incoterm_id.name"/>
|
||||
</t>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
</odoo>
|
||||
3
commercial_invoice/security/ir.model.access.csv
Normal file
3
commercial_invoice/security/ir.model.access.csv
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_commercial_invoice_user,commercial.invoice.user,model_commercial_invoice,account.group_account_invoice,1,1,1,1
|
||||
access_commercial_invoice_manager,commercial.invoice.manager,model_commercial_invoice,account.group_account_manager,1,1,1,1
|
||||
|
17
commercial_invoice/views/account_move_views.xml
Normal file
17
commercial_invoice/views/account_move_views.xml
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="view_move_list_inherit_commercial_invoice" model="ir.ui.view">
|
||||
<field name="name">account.move.list.commercial.invoice</field>
|
||||
<field name="model">account.move</field>
|
||||
<field name="inherit_id" ref="account.view_out_invoice_tree"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//list" position="inside">
|
||||
<header>
|
||||
<button name="action_create_commercial_invoice"
|
||||
type="object"
|
||||
string="Create Commercial Invoice"/>
|
||||
</header>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
128
commercial_invoice/views/commercial_invoice_views.xml
Normal file
128
commercial_invoice/views/commercial_invoice_views.xml
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- Tree View -->
|
||||
<record id="commercial_invoice_view_tree" model="ir.ui.view">
|
||||
<field name="name">commercial.invoice.tree</field>
|
||||
<field name="model">commercial.invoice</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="Commercial Invoices" decoration-info="state == 'draft'" decoration-muted="state == 'cancelled'">
|
||||
<field name="name"/>
|
||||
<field name="date"/>
|
||||
<field name="partner_id"/>
|
||||
<field name="invoice_amount"/>
|
||||
<field name="total_amount"/>
|
||||
<field name="currency_id" invisible="1"/>
|
||||
<field name="state"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Form View -->
|
||||
<record id="commercial_invoice_view_form" model="ir.ui.view">
|
||||
<field name="name">commercial.invoice.form</field>
|
||||
<field name="model">commercial.invoice</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Commercial Invoice">
|
||||
<header>
|
||||
<button name="action_confirm" string="Confirm" type="object" class="oe_highlight"
|
||||
invisible="state != 'draft'"/>
|
||||
<button name="action_draft" string="Reset to Draft" type="object"
|
||||
invisible="state == 'draft'"/>
|
||||
<button name="action_cancel" string="Cancel" type="object"
|
||||
invisible="state == 'cancelled'"/>
|
||||
<button name="%(action_report_commercial_invoice)d" string="Print" type="action" class="oe_highlight"/>
|
||||
<field name="state" widget="statusbar"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<div class="oe_title">
|
||||
<h1>
|
||||
<field name="name" readonly="1"/>
|
||||
</h1>
|
||||
</div>
|
||||
<group>
|
||||
<group>
|
||||
<field name="partner_id" readonly="state != 'draft'"/>
|
||||
<field name="importer_id" readonly="state != 'draft'"/>
|
||||
<field name="customs_broker_id" readonly="state != 'draft'"/>
|
||||
<field name="payment_term_id" readonly="state != 'draft'"/>
|
||||
<field name="incoterm_id" readonly="state != 'draft'"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="date" readonly="state != 'draft'"/>
|
||||
<field name="company_id" groups="base.group_multi_company" readonly="state != 'draft'"/>
|
||||
<field name="currency_id" groups="base.group_multi_currency" readonly="state != 'draft'"/>
|
||||
<field name="number_of_packages" readonly="state != 'draft'"/>
|
||||
<field name="total_weight" readonly="state != 'draft'"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Invoices" name="invoices">
|
||||
<field name="invoice_ids" readonly="state != 'draft'">
|
||||
<list>
|
||||
<field name="name" readonly="1"/>
|
||||
<field name="partner_id" readonly="1"/>
|
||||
<field name="invoice_date" readonly="1"/>
|
||||
<field name="amount_total" readonly="1"/>
|
||||
</list>
|
||||
</field>
|
||||
<group class="oe_subtotal_footer">
|
||||
<field name="invoice_amount" class="oe_subtotal_footer_separator"/>
|
||||
<field name="packaging_cost" widget="monetary"/>
|
||||
<field name="freight_cost" widget="monetary"/>
|
||||
<field name="insurance_cost" widget="monetary"/>
|
||||
<field name="other_cost" widget="monetary"/>
|
||||
<field name="total_amount" class="oe_subtotal_footer_separator" widget="monetary"/>
|
||||
</group>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
<chatter/>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Search View -->
|
||||
<record id="commercial_invoice_view_search" model="ir.ui.view">
|
||||
<field name="name">commercial.invoice.search</field>
|
||||
<field name="model">commercial.invoice</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search Commercial Invoices">
|
||||
<field name="name"/>
|
||||
<field name="partner_id"/>
|
||||
<field name="invoice_ids"/>
|
||||
<filter string="Draft" name="draft" domain="[('state','=','draft')]"/>
|
||||
<filter string="Done" name="done" domain="[('state','=','done')]"/>
|
||||
<group expand="0" string="Group By">
|
||||
<filter string="Status" name="status" context="{'group_by':'state'}"/>
|
||||
<filter string="Partner" name="partner" context="{'group_by':'partner_id'}"/>
|
||||
<filter string="Date" name="date" context="{'group_by':'date'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Action -->
|
||||
<record id="action_commercial_invoice" model="ir.actions.act_window">
|
||||
<field name="name">Commercial Invoices</field>
|
||||
<field name="res_model">commercial.invoice</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
<field name="context">{'search_default_draft': 1}</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Create your first commercial invoice
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Menu -->
|
||||
<menuitem id="menu_commercial_invoice_root"
|
||||
name="Commercial Invoices"
|
||||
parent="account.menu_finance"
|
||||
sequence="5"/>
|
||||
|
||||
<menuitem id="menu_commercial_invoice"
|
||||
name="Commercial Invoices"
|
||||
parent="menu_commercial_invoice_root"
|
||||
action="action_commercial_invoice"
|
||||
sequence="1"/>
|
||||
</odoo>
|
||||
1
qc_partner_default_lang/__init__.py
Normal file
1
qc_partner_default_lang/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
from . import models
|
||||
14
qc_partner_default_lang/__manifest__.py
Normal file
14
qc_partner_default_lang/__manifest__.py
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
'name': 'Partner Default Language by Location',
|
||||
'version': '18.0.1.0.0',
|
||||
'category': 'Tools',
|
||||
'summary': 'Automatically set partner language based on state/country',
|
||||
'author': 'Bemade',
|
||||
'website': 'https://www.bemade.org',
|
||||
'depends': ['base'],
|
||||
'data': [],
|
||||
'installable': True,
|
||||
'application': False,
|
||||
'auto_install': False,
|
||||
'license': 'LGPL-3',
|
||||
}
|
||||
1
qc_partner_default_lang/models/__init__.py
Normal file
1
qc_partner_default_lang/models/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
from . import res_partner
|
||||
34
qc_partner_default_lang/models/res_partner.py
Normal file
34
qc_partner_default_lang/models/res_partner.py
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
from odoo import api, models
|
||||
|
||||
|
||||
class ResPartner(models.Model):
|
||||
_inherit = 'res.partner'
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
for vals in vals_list:
|
||||
# Only set language if it's not already specified
|
||||
if not vals.get('lang'):
|
||||
state = vals.get('state_id')
|
||||
country = vals.get('country_id')
|
||||
|
||||
if state:
|
||||
state_rec = self.env['res.country.state'].browse(state)
|
||||
# Check if the state is Quebec
|
||||
if state_rec.code == 'QC' and state_rec.country_id.code == 'CA':
|
||||
vals['lang'] = 'fr_CA'
|
||||
else:
|
||||
vals['lang'] = 'en_US'
|
||||
else:
|
||||
vals['lang'] = 'en_US'
|
||||
|
||||
return super().create(vals_list)
|
||||
|
||||
@api.onchange('state_id', 'country_id')
|
||||
def _onchange_location_set_lang(self):
|
||||
# Only suggest language change if it's a new record (id not set yet)
|
||||
if not self.id and not self.lang:
|
||||
if self.state_id and self.state_id.code == 'QC' and self.state_id.country_id.code == 'CA':
|
||||
self.lang = 'fr_CA'
|
||||
else:
|
||||
self.lang = 'en_US'
|
||||
27
qc_partner_default_lang/static/description/index.html
Normal file
27
qc_partner_default_lang/static/description/index.html
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
<![CDATA[<section class="oe_container">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan" style="color:#875A7B;">Partner Default Language by Location</h2>
|
||||
<h3 class="oe_slogan">Automatic Language Assignment for Partners</h3>
|
||||
<div class="oe_demo oe_picture oe_screenshot">
|
||||
<p class="oe_mt32 text-center">
|
||||
This module automatically sets the default language for new partners based on their location.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="oe_container">
|
||||
<div class="oe_row oe_spaced">
|
||||
<div class="oe_span12">
|
||||
<h2 class="oe_slogan" style="color:#875A7B;">Features</h2>
|
||||
<div class="oe_demo oe_picture oe_screenshot">
|
||||
<ul>
|
||||
<li>Automatically sets French (fr_CA) for partners in Quebec</li>
|
||||
<li>Defaults to English (en_US) for all other locations</li>
|
||||
<li>Works on partner creation and when updating location fields</li>
|
||||
<li>Respects manually set language preferences</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>]]>
|
||||
Loading…
Reference in a new issue