Skip to content
Extraits de code Groupes Projets

Comparer les révisions

Les modifications sont affichées comme si la révision source était fusionnée avec la révision cible. En savoir plus sur la comparaison des révisions.

Source

Sélectionner le projet cible
No results found
Sélectionner une révision Git
  • 14.0
  • 14.0-ahp12
  • 14.0-accessory
  • 14.0-double-paillage
4 résultats

Cible

Sélectionner le projet cible
  • lefilament/ap/ap_sale_project
1 résultat
Sélectionner une révision Git
  • 14.0
  • 14.0-ahp12
  • 14.0-accessory
  • 14.0-double-paillage
4 résultats
Afficher les modifications

Commits on Source 2

Affichage de
avec 549 ajouts et 2 suppressions
......@@ -2,3 +2,5 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)
from . import models
from . import wizard
from . import controllers
......@@ -23,8 +23,11 @@
"views/sale_project_admin_state_views.xml",
"views/product_template_views.xml",
"views/res_partner_views.xml",
"views/sale_intervention_location_view.xml",
"views/sale_project_plant_goal_view.xml",
# views menu
# wizard
"wizard/ap_export_wizard_views.xml",
],
"qweb": [
# "static/src/xml/*.xml",
......
from . import export
# Copyright 2021 Le Filament (<http://www.le-filament.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import io
import xlsxwriter
from odoo import http
from odoo.http import request
def export_xlsx(data_by_sheet):
"""
Génère un fichier XLSX avec plusieurs feuilles.
:param data_by_sheet: dict où chaque clé est un nom de feuille,
et chaque valeur est une liste de dictionnaires.
Exemple :
{
'Feuille1': [{'Nom': 'Alice', 'Age': 30}],
'Feuille2': [{'Produit': 'Widget', 'Prix': 20.5}]
}
:return: binary XLSX content
"""
output = io.BytesIO()
workbook = xlsxwriter.Workbook(output, {"in_memory": True})
for sheet_name, rows in data_by_sheet.items():
sheet = workbook.add_worksheet(sheet_name[:31]) # Excel limite à 31 caractères
if not rows:
continue
headers = list(rows[0].keys())
for col_idx, header in enumerate(headers):
if header[:3] == "ROT":
cell_format = workbook.add_format()
cell_format.set_rotation(90)
sheet.write(0, col_idx, header[3:], cell_format)
else:
sheet.write(0, col_idx, header)
for row_idx, row in enumerate(rows, start=1):
for col_idx, header in enumerate(headers):
sheet.write(row_idx, col_idx, row.get(header, ""))
workbook.close()
output.seek(0)
return output.read()
class ApExportController(http.Controller):
def get_filename(self, type, saison):
prefix = {
"order": "suivi_commande",
"fin": "suivi_financeurs",
"tech": "suivi_technique",
}
return f"{prefix.get(type, 'fichier')}_{saison.name}.xlsx"
def get_nb_staples(self, inter_id):
staples = 0
if inter_id.mulch_has_staples:
staples += inter_id.mulch_staples_qty * inter_id.mulch_qty
if inter_id.mulch2_has_staples:
staples += inter_id.mulch2_staples_qty * inter_id.mulch2_qty
def get_linked_contact(self, partner_id):
if partner_id.child_ids:
return ",".join([child.name for child in partner_id.child_ids])
return ""
def get_financial_help(self, inter):
if inter.financial_help_ids:
return ",".join([h.name for h in inter.financial_help_ids])
return ""
def get_nb_tree(self, inter_id):
if inter_id.plant_sequence_ids:
to_count = inter_id.plant_sequence_ids
else:
to_count = inter_id.plant_list_ids
trees = to_count.search([("scale", "=", "big")])
return len(trees)
def get_order_data(self, saison):
"""
export commande
"""
# un onglet par programme (subvention)
programmes = request.env["sale.project.subvention"].search([])
# fichier final
file = {}
for programme in programmes:
page = []
# tout les projets sous ce programme pour la saison dont le devis est signé
projects = request.env["sale.project"].search(
[
("saison_id", "=", saison.id),
("project_subvention_id", "=", programme.id),
("sale_order_id.state", "=", "sale"),
]
)
interventions = request.env["sale.intervention"].search(
[("project_id", "in", projects.ids)]
)
plant_data = {}
# sauvegarde de la liste des planteurs pour la premiere ligne
seeders_list = []
for intervention in interventions:
seeders_list.append(intervention.partner_id)
for sequence in intervention.plant_sequence_ids:
plant = plant_data.setdefault(sequence.product_id, {})
if plant.get(f"ROT{intervention.partner_id.name}"):
plant[f"ROT{intervention.partner_id.name}"] += sequence.qty
else:
plant[f"ROT{intervention.partner_id.name}"] = sequence.qty
# format data for xls
for plant in plant_data:
line = {}
line.update(
{
"Article": plant.name,
"Nom latin": plant.latin_name or "",
"Nom de la catégorie": plant.categ_id.name or "",
"Numéro de pépiniériste": plant.categ_id.nurseryman_id or "",
}
)
qty = plant_data.get(plant)
for seeder in qty:
line[seeder] = qty[seeder]
page.append(line)
# si on des commandes pour ce programe on l ajoute au fichier
if page:
# on construit la premier ligne avec les secteurs
# de plantation par planteur
first_line = {
"Article": "Secteur plantation",
"Nom latin": "",
"Nom de la catégorie": "",
"Numéro de pépiniériste": "",
}
for seeder in set(seeders_list):
secteur_plat = (
request.env["sale.project"]
.search([("partner_id", "=", seeder.id)])
.mapped("plant_sector_id")
.mapped("name")
)
first_line[f"ROT{seeder.name}"] = ",".join(secteur_plat)
page.insert(0, first_line)
file[programme.name] = page
return file
def get_fin_data(self, saison):
"""
export suivi financeurs
"""
sheet_name = "suivi financeurs"
file = {sheet_name: []}
projects = request.env["sale.project"].search([("saison_id", "=", saison.id)])
for project in projects:
if project.intervention_ids:
for inter in project.intervention_ids:
project_data = {
"Saison": saison.name or "",
"Programme": project.project_subvention_id.name or "",
"Numéro client": project.partner_id.ref or "",
"Secteur de plantation": project.plant_sector_id.name or "",
"Secteur de livraison": project.delivery_sector_id.name or "",
"Réferent": project.user_id.name or "",
"Planteur": project.partner_id.name or "",
"Contacts": self.get_linked_contact(project.partner_id),
"Adresse (rue)": project.partner_id.street or "",
"Adresse (rue 2)": project.partner_id.street2 or "",
"Code postal": project.partner_id.zip or "",
"Ville": project.partner_id.city or "",
"Télephone": project.partner_id.phone or "",
"Email": project.partner_id.email or "",
"Commune intervention": inter.city or "",
"Geo latitude": str(inter.latitude).replace(",", "."),
"Geo longitude": str(inter.longitude).replace(",", "."),
"Bord de départementale": inter.near_road or "",
"Forme juridique": project.partner_id.partner_company_type_id.name
or "",
"Type de culture": project.partner_id.culture_type_id.name
or "",
"Date de première adhésion": (
project.partner_id.year_1st_membership.strftime("%d-%m-%Y")
if project.partner_id.year_1st_membership
else ""
),
"Diffusion sur site internet": project.online or "",
"Longueur de haie": inter.intervention_length or "",
"Nombre de plants": inter.plant_qty or "",
"Surface (en m²)": inter.surface or "",
"Dont nombre total d'arbres": self.get_nb_tree(inter),
"Type d'intervention": inter.intervention_type_id.name or "",
"Implantation": inter.location_id.name or "",
"Objectif plantation": project.plant_goal_id.name or "",
"Avancement": project.admin_state_id.name,
"Aides financières": self.get_financial_help(inter),
}
file.get(sheet_name).append(project_data)
return file
def get_tech_data(self, saison):
"""
export suivi technique
"""
sheet_name = "suivi technique"
file = {sheet_name: []}
projects = request.env["sale.project"].search([("saison_id", "=", saison.id)])
for project in projects:
if project.intervention_ids:
for inter in project.intervention_ids:
project_data = {
"Saison": saison.name or "",
"Programme": project.project_subvention_id.name or "",
"Client": project.partner_id.ref or "",
"Secteur de plantation": project.plant_sector_id.name or "",
"Secteur de livraison": project.delivery_sector_id.name or "",
"Réferent": project.user_id.name or "",
"Planteur": project.partner_id.name or "",
"Contacts": self.get_linked_contact(project.partner_id),
"Adresse (rue)": project.partner_id.street or "",
"Adresse (rue 2)": project.partner_id.street2 or "",
"Code postal": project.partner_id.zip or "",
"Ville": project.partner_id.city or "",
"Télephone": project.partner_id.phone or "",
"Email": project.partner_id.email or "",
"Commune intervention": inter.city or "",
"Longueur de haie": inter.intervention_length or "",
"Nombre de plants": inter.plant_qty or "",
"Surface (en m²)": inter.surface or "",
"Dont nombre total d'arbres": self.get_nb_tree(inter),
"Type d'intervention": inter.intervention_type_id.name or "",
"Paillage 1": inter.mulch_id.name or "",
"Paillage 2": inter.mulch2_id.name or "",
"Qté paillage 1": inter.mulch_qty or "",
"Qté paillage 2": inter.mulch2_qty or "",
"Nom fournisseur copeaux/écorce": project.bark_supplier or "",
"Qté protegée haut": inter.high_protection_qty or "",
"Qté piquets": inter.stake_qty or "",
"Qté protegée bas": inter.low_protection_qty or "",
"Qté bambous": inter.bamboo_qty or "",
"Nombre d'agrafes": self.get_nb_staples(inter),
"Nombre de collerettes": inter.collarette_qty or "",
"Mois livraison": project.delivery_month or "",
"Accueil démo": project.demo or "",
"Commentaires": project.comment or "",
}
file.get(sheet_name).append(project_data)
return file
@http.route("/ap_export/xlsx_download", type="http", auth="user")
def download_xlsx(self, wizard_id, **kwargs):
wizard = request.env["ap.export.wizard"].browse(int(wizard_id))
filename = self.get_filename(wizard.export_type, wizard.saison_id)
data_methods = {
"order": self.get_order_data,
"fin": self.get_fin_data,
"tech": self.get_tech_data,
}
xlsx_data = export_xlsx(data_methods.get(wizard.export_type)(wizard.saison_id))
headers = [
(
"Content-Type",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
),
("Content-Disposition", 'attachment; filename="%s"' % filename),
]
return request.make_response(xlsx_data, headers)
......@@ -12,6 +12,7 @@ class ProductCategory(models.Model):
# ------------------------------------------------------
symbol = fields.Char("Symbole")
accessory_label = fields.Char("Nom de l'accessoire associé")
nurseryman_id = fields.Char("Numéro de pépinieriste")
# ------------------------------------------------------
# SQL Constraints
......
......@@ -13,6 +13,9 @@ class ProductTemplate(models.Model):
nb_accesories = fields.Float("Nombre d'accessoires")
accessory_label = fields.Char(related="categ_id.accessory_label", readonly=True)
mulch_quantity_multiplier = fields.Float("Multiplicateur paillage", default=1.0)
latin_name = fields.Char(
string="Nom latin"
)
# ------------------------------------------------------
# SQL Constraints
......
......@@ -439,6 +439,13 @@ class SaleIntervention(models.Model):
inverse_name="sale_intervention_id",
string="Articles à déstocker",
)
location_id = fields.Many2one(
"sale.intervention.location",
"Implantation",
ondelete="restrict",
)
near_road = fields.Char("Bord de départementale")
# ------------------------------------------------------
# SQL Constraints
......@@ -1041,3 +1048,11 @@ class SaleIntervention(models.Model):
res = super().unlink()
project_id.update_order_lines()
return res
class SaleInterventionLocation(models.Model):
_name = "sale.intervention.location"
_description = "Implantation"
_order = "name"
name = fields.Char(string="Implantation", required=True)
......@@ -46,6 +46,14 @@ class SaleInterventionPlantSequence(models.Model):
qty = fields.Integer("Qté par espèces")
sequence = fields.Integer(string="Ordre dans la séquence", default=10)
is_list = fields.Boolean("Est une construction liste")
scale = fields.Selection(
[
("little", "Petit"),
("middle", "Moyen"),
("big", "Grand"),
],
string="Taille",
)
# ------------------------------------------------------
# SQL Constraints
......
......@@ -124,7 +124,14 @@ class SaleProject(models.Model):
readonly=True,
states={"draft": [("readonly", False)], "sent": [("readonly", False)]},
)
plant_sector_id = fields.Many2one(
comodel_name="res.partner.geo.sector", string="Secteur de plantation"
)
delivery_sector_id = fields.Many2one(
comodel_name="res.partner.geo.sector", string="Secteur de livraison"
)
bark_supplier = fields.Char("Fournisseur de copeaux/écorce")
comment = fields.Text("Commentaires")
saison_id = fields.Many2one(
"sale.project.saison",
"Saison",
......@@ -132,6 +139,44 @@ class SaleProject(models.Model):
ondelete="restrict",
)
date_visit = fields.Date("Date de visite")
delivery_month = fields.Selection(
[
("1", "Janvier"),
("2", "Février"),
("3", "Mars"),
("4", "Avril"),
("5", "Mai"),
("6", "Juin"),
("7", "Juillet"),
("8", "Août"),
("9", "Septembre"),
("10", "Octobre"),
("11", "Novembre"),
("12", "Décembre"),
],
string="Mois de livraison",
)
demo = fields.Selection(
[
("yes", "Oui"),
("no", "Non"),
],
string="Accueil démo",
)
online = fields.Selection(
[
("yes", "Oui"),
("no", "Non"),
],
string="Diffusion sur site internet",
)
plant_goal_id = fields.Many2one(
"sale.project.plant.goal",
"Objectif plantation",
ondelete="restrict",
)
intervention_ids = fields.One2many(
comodel_name="sale.intervention",
......@@ -385,6 +430,14 @@ class SaleProjectSaison(models.Model):
end_date = fields.Date("Date de fin", required=True)
class SaleProjectPlantGoal(models.Model):
_name = "sale.project.plant.goal"
_description = "Objectif plantation"
_order = "name"
name = fields.Char(string="Objectif plantation", required=True)
class SaleProjectAdminState(models.Model):
_name = "sale.project.admin.state"
_description = "Étapes du projet"
......
......@@ -8,4 +8,8 @@ sale_financial_help_salesteam,sale_financial_help_salesteam,model_sale_financial
sale_intervention_salesteam,sale_intervention_salesteam,model_sale_intervention,sales_team.group_sale_salesman,1,1,1,1
sale_intervention_stock_salesteam,sale_intervention_stock_salesteam,model_sale_intervention_stock,sales_team.group_sale_salesman,1,1,1,1
sale_intervention_plant_sequence_salesteam,sale_intervention_plant_sequence_salesteam,model_sale_intervention_plant_sequence,sales_team.group_sale_salesman,1,1,1,1
sale_intervention_location_salesteam,sale_intervention_location_salesteam,model_sale_intervention_location,sales_team.group_sale_salesman,1,1,1,1
sale_project_subvention_salesteam,sale_project_subvention_salesteam,model_sale_project_subvention,sales_team.group_sale_salesman,1,1,1,1
sale_project_plant_goal_salesteam,sale_project_plant_goal_salesteam,model_sale_project_plant_goal,sales_team.group_sale_salesman,1,1,1,1
access_ap_export_wizard,access_ap_export_wizard,model_ap_export_wizard,sales_team.group_sale_salesman,1,1,1,0
......@@ -9,7 +9,19 @@
<field name="parent_id" position="after">
<field name="symbol" />
<field name="accessory_label" />
<field name="name" />
<field name="nurseryman_id" />
</field>
</field>
</record>
<record id="product_template_form_view" model="ir.ui.view">
<field name="name">product.template.form.inherit</field>
<field name="model">product.template</field>
<field name="inherit_id" ref="product.product_template_form_view" />
<field name="arch" type="xml">
<field name="type" position="after">
<field name="latin_name" />
</field>
</field>
</record>
......
<odoo>
<!-- List View-->
<record id="sale_intervention_admin_location_view_list" model="ir.ui.view">
<field name="name">Implantation</field>
<field name="model">sale.intervention.location</field>
<field name="arch" type="xml">
<tree editable="bottom">
<field name="name" />
</tree>
</field>
</record>
<!-- Actions opening views on models -->
<record model="ir.actions.act_window" id="sale_intervention_admin_location_act_window">
<field name="name">Implantation</field>
<field name="res_model">sale.intervention.location</field>
<field name="view_mode">tree</field>
</record>
<!-- Menu Items -->
<menuitem
id="menu_sale_intervention_admin_goal"
name="Implantation"
parent="ap_sale_project.menu_sale_project_config"
action="sale_intervention_admin_location_act_window"
sequence="22"
/>
</odoo>
......@@ -25,6 +25,8 @@
name="intervention_type_id"
options="{'no_open': True, 'no_create': True}"
/>
<field name="location_id" />
<field name="near_road" />
<field name="intervention_uom_name" invisible="True" />
</group>
<group>
......@@ -240,6 +242,7 @@
options="{'no_open': True, 'no_create': True}"
/>
<field name="is_local" />
<field name="scale" />
</tree>
</field>
<group
......
<odoo>
<!-- List View-->
<record id="sale_project_admin_goal_view_list" model="ir.ui.view">
<field name="name">Objectif plantation</field>
<field name="model">sale.project.plant.goal</field>
<field name="arch" type="xml">
<tree editable="bottom">
<field name="name" />
</tree>
</field>
</record>
<!-- Actions opening views on models -->
<record model="ir.actions.act_window" id="sale_project_admin_location_act_window">
<field name="name">Objectif plantation</field>
<field name="res_model">sale.project.plant.goal</field>
<field name="view_mode">tree</field>
</record>
<!-- Menu Items -->
<menuitem
id="menu_sale_project_admin_goal"
name="Objectif plantation"
parent="ap_sale_project.menu_sale_project_config"
action="sale_project_admin_location_act_window"
sequence="21"
/>
</odoo>
......@@ -38,11 +38,19 @@
<group>
<field name="project_subvention_id" readonly="True" />
<field name="geo_sector_id" />
<field name="plant_sector_id" />
<field name="delivery_sector_id" />
<field name="user_id" />
<field name="bark_supplier" />
</group>
<group>
<field name="saison_id" />
<field name="date_visit" />
<field name="comment" />
<field name="delivery_month" />
<field name="demo" />
<field name="plant_goal_id" />
<field name="online" />
<field name="state" invisible="1" />
</group>
</group>
......
from . import ap_export_wizard
# Copyright 2021 Le Filament (<http://www.le-filament.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import models, fields
class ApExportWizard(models.TransientModel):
_name = "ap.export.wizard"
_description = "Export de données"
export_type = fields.Selection(
[
("order", "Commandes"),
("fin", "Suivi financeurs"),
("tech", "Suivi technique"),
],
required=True,
default="order",
)
saison_id = fields.Many2one(
comodel_name="sale.project.saison", string="Saison", required=True
)
def action_exporter(self):
return {
"type": "ir.actions.act_url",
"url": f"/ap_export/xlsx_download?wizard_id={self.id}",
"target": "self",
}
<?xml version="1.0" ?>
<!-- Copyright 2025 Le Filament
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -->
<odoo>
<data>
<record id="view_ap_export_wizard_form" model="ir.ui.view">
<field name="name">ap.export.wizard.form</field>
<field name="model">ap.export.wizard</field>
<field name="arch" type="xml">
<form string="Export de données">
<group>
<field name="export_type"/>
</group>
<group>
<field name="saison_id"/>
</group>
<footer>
<button string="Valider" type="object" name="action_exporter" class="btn-primary"/>
<button string="Annuler" class="btn-secondary" special="cancel"/>
</footer>
</form>
</field>
</record>
<record id="action_ap_export_wizard" model="ir.actions.act_window">
<field name="name">Export de données</field>
<field name="res_model">ap.export.wizard</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
<menuitem
id="menu_export_root"
name="Export de données"
parent="sale.sale_menu_root"
sequence="7"
groups="sales_team.group_sale_salesman"
/>
<menuitem
id="menu_export_wizard"
parent="menu_export_root"
name="Exporter"
action="action_ap_export_wizard"
sequence="10"
groups="sales_team.group_sale_salesman"
/>
</data>
</odoo>