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