odoo-addon-pms 16.0.0.29.0__py3-none-any.whl → 16.0.2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- odoo/addons/pms/README.rst +1 -1
- odoo/addons/pms/__manifest__.py +6 -5
- odoo/addons/pms/data/pms_data.xml +0 -11
- odoo/addons/pms/i18n/de.po +79 -142
- odoo/addons/pms/i18n/es.po +79 -142
- odoo/addons/pms/i18n/it.po +26 -138
- odoo/addons/pms/i18n/pms.pot +18 -302
- odoo/addons/pms/migrations/16.0.1.0.0/pre-migration.py +48 -0
- odoo/addons/pms/models/__init__.py +0 -2
- odoo/addons/pms/models/pms_checkin_partner.py +98 -376
- odoo/addons/pms/models/pms_folio.py +0 -1
- odoo/addons/pms/models/pms_reservation.py +0 -1
- odoo/addons/pms/models/res_company.py +0 -6
- odoo/addons/pms/models/res_partner.py +0 -366
- odoo/addons/pms/static/description/index.html +1 -1
- odoo/addons/pms/tests/test_pms_checkin_partner.py +15 -453
- odoo/addons/pms/tests/test_pms_folio.py +0 -6
- odoo/addons/pms/tests/test_pms_reservation.py +7 -53
- odoo/addons/pms/tests/test_pms_reservation_line.py +0 -3
- odoo/addons/pms/views/pms_checkin_partner_views.xml +11 -32
- odoo/addons/pms/views/res_company_views.xml +0 -1
- odoo/addons/pms/views/res_partner_views.xml +0 -40
- {odoo_addon_pms-16.0.0.29.0.dist-info → odoo_addon_pms-16.0.2.0.0.dist-info}/METADATA +3 -3
- {odoo_addon_pms-16.0.0.29.0.dist-info → odoo_addon_pms-16.0.2.0.0.dist-info}/RECORD +26 -29
- odoo/addons/pms/models/res_partner_id_category.py +0 -14
- odoo/addons/pms/models/res_partner_id_number.py +0 -141
- odoo/addons/pms/views/res_partner_id_category_views.xml +0 -29
- odoo/addons/pms/views/res_partner_id_number_views.xml +0 -29
- {odoo_addon_pms-16.0.0.29.0.dist-info → odoo_addon_pms-16.0.2.0.0.dist-info}/WHEEL +0 -0
- {odoo_addon_pms-16.0.0.29.0.dist-info → odoo_addon_pms-16.0.2.0.0.dist-info}/top_level.txt +0 -0
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
import logging
|
|
5
5
|
|
|
6
6
|
from odoo import _, api, fields, models
|
|
7
|
-
from odoo.exceptions import ValidationError
|
|
8
7
|
|
|
9
8
|
_logger = logging.getLogger(__name__)
|
|
10
9
|
|
|
@@ -88,51 +87,9 @@ class ResPartner(models.Model):
|
|
|
88
87
|
inverse_name="partner_id",
|
|
89
88
|
)
|
|
90
89
|
|
|
91
|
-
country_id = fields.Many2one(
|
|
92
|
-
readonly=False,
|
|
93
|
-
store=True,
|
|
94
|
-
index=True,
|
|
95
|
-
compute="_compute_country_id",
|
|
96
|
-
)
|
|
97
|
-
state_id = fields.Many2one(
|
|
98
|
-
readonly=False,
|
|
99
|
-
store=True,
|
|
100
|
-
index=True,
|
|
101
|
-
compute="_compute_state_id",
|
|
102
|
-
)
|
|
103
|
-
city = fields.Char(
|
|
104
|
-
readonly=False,
|
|
105
|
-
store=True,
|
|
106
|
-
compute="_compute_city",
|
|
107
|
-
)
|
|
108
|
-
street = fields.Char(
|
|
109
|
-
readonly=False,
|
|
110
|
-
store=True,
|
|
111
|
-
compute="_compute_street",
|
|
112
|
-
)
|
|
113
|
-
street2 = fields.Char(
|
|
114
|
-
readonly=False,
|
|
115
|
-
store=True,
|
|
116
|
-
compute="_compute_street2",
|
|
117
|
-
)
|
|
118
|
-
zip = fields.Char(
|
|
119
|
-
readonly=False,
|
|
120
|
-
store=True,
|
|
121
|
-
compute="_compute_zip",
|
|
122
|
-
)
|
|
123
90
|
comment = fields.Text(
|
|
124
91
|
tracking=True,
|
|
125
92
|
)
|
|
126
|
-
reservation_possible_customer_id = fields.Many2one(
|
|
127
|
-
string="Possible Customer In Reservation", comodel_name="pms.reservation"
|
|
128
|
-
)
|
|
129
|
-
folio_possible_customer_id = fields.Many2one(
|
|
130
|
-
string="Possible Customer In Folio", comodel_name="pms.folio"
|
|
131
|
-
)
|
|
132
|
-
checkin_partner_possible_customer_id = fields.Many2one(
|
|
133
|
-
string="Possible Customer In Checkin Partner",
|
|
134
|
-
comodel_name="pms.checkin.partner",
|
|
135
|
-
)
|
|
136
93
|
invoicing_policy = fields.Selection(
|
|
137
94
|
help="""The invoicing policy of the partner,
|
|
138
95
|
set Property to user the policy configured in the Property""",
|
|
@@ -151,208 +108,6 @@ class ResPartner(models.Model):
|
|
|
151
108
|
string="Days from Checkout",
|
|
152
109
|
help="Days from Checkout to generate the invoice",
|
|
153
110
|
)
|
|
154
|
-
residence_street = fields.Char(
|
|
155
|
-
string="Street of residence",
|
|
156
|
-
help="Street of the guest's residence",
|
|
157
|
-
readonly=False,
|
|
158
|
-
store=True,
|
|
159
|
-
compute="_compute_residence_street",
|
|
160
|
-
)
|
|
161
|
-
residence_street2 = fields.Char(
|
|
162
|
-
string="Second street of residence",
|
|
163
|
-
help="Second street of the guest's residence",
|
|
164
|
-
readonly=False,
|
|
165
|
-
store=True,
|
|
166
|
-
compute="_compute_residence_street2",
|
|
167
|
-
)
|
|
168
|
-
residence_zip = fields.Char(
|
|
169
|
-
string="Zip of residence",
|
|
170
|
-
help="Zip of the guest's residence",
|
|
171
|
-
readonly=False,
|
|
172
|
-
store=True,
|
|
173
|
-
compute="_compute_residence_zip",
|
|
174
|
-
change_default=True,
|
|
175
|
-
)
|
|
176
|
-
residence_city = fields.Char(
|
|
177
|
-
string="city of residence",
|
|
178
|
-
help="City of the guest's residence",
|
|
179
|
-
readonly=False,
|
|
180
|
-
store=True,
|
|
181
|
-
compute="_compute_residence_city",
|
|
182
|
-
)
|
|
183
|
-
residence_country_id = fields.Many2one(
|
|
184
|
-
string="Country of residence",
|
|
185
|
-
help="Partner country of residence",
|
|
186
|
-
readonly=False,
|
|
187
|
-
store=True,
|
|
188
|
-
index=True,
|
|
189
|
-
compute="_compute_residence_country_id",
|
|
190
|
-
comodel_name="res.country",
|
|
191
|
-
)
|
|
192
|
-
residence_state_id = fields.Many2one(
|
|
193
|
-
string="State of residence",
|
|
194
|
-
help="Partner state of residence",
|
|
195
|
-
readonly=False,
|
|
196
|
-
store=True,
|
|
197
|
-
index=True,
|
|
198
|
-
compute="_compute_residence_state_id",
|
|
199
|
-
comodel_name="res.country.state",
|
|
200
|
-
)
|
|
201
|
-
|
|
202
|
-
# pylint: disable=W8110
|
|
203
|
-
@api.depends("pms_checkin_partner_ids", "pms_checkin_partner_ids.residence_street")
|
|
204
|
-
def _compute_residence_street(self):
|
|
205
|
-
if hasattr(super(), "_compute_residence_street"):
|
|
206
|
-
super()._compute_residence_street()
|
|
207
|
-
for record in self:
|
|
208
|
-
if record.pms_checkin_partner_ids:
|
|
209
|
-
last_update_street = record.pms_checkin_partner_ids.filtered(
|
|
210
|
-
lambda x, r=record: x.write_date
|
|
211
|
-
== max(r.pms_checkin_partner_ids.mapped("write_date"))
|
|
212
|
-
)
|
|
213
|
-
if last_update_street and last_update_street[0].residence_street:
|
|
214
|
-
record.residence_street = last_update_street[0].residence_street
|
|
215
|
-
|
|
216
|
-
# pylint: disable=W8110
|
|
217
|
-
@api.depends("pms_checkin_partner_ids", "pms_checkin_partner_ids.residence_street2")
|
|
218
|
-
def _compute_residence_street2(self):
|
|
219
|
-
if hasattr(super(), "_compute_residence_street2"):
|
|
220
|
-
super()._compute_residence_street2()
|
|
221
|
-
for record in self:
|
|
222
|
-
if record.pms_checkin_partner_ids:
|
|
223
|
-
last_update_street2 = record.pms_checkin_partner_ids.filtered(
|
|
224
|
-
lambda x, r=record: x.write_date
|
|
225
|
-
== max(r.pms_checkin_partner_ids.mapped("write_date"))
|
|
226
|
-
)
|
|
227
|
-
if last_update_street2 and last_update_street2[0].residence_street2:
|
|
228
|
-
record.residence_street2 = last_update_street2[0].residence_street2
|
|
229
|
-
|
|
230
|
-
# pylint: disable=W8110
|
|
231
|
-
@api.depends("pms_checkin_partner_ids", "pms_checkin_partner_ids.residence_zip")
|
|
232
|
-
def _compute_residence_zip(self):
|
|
233
|
-
if hasattr(super(), "_compute_residence_zip"):
|
|
234
|
-
super()._compute_residence_zip()
|
|
235
|
-
for record in self:
|
|
236
|
-
if record.pms_checkin_partner_ids:
|
|
237
|
-
last_update_zip = record.pms_checkin_partner_ids.filtered(
|
|
238
|
-
lambda x, r=record: x.write_date
|
|
239
|
-
== max(r.pms_checkin_partner_ids.mapped("write_date"))
|
|
240
|
-
)
|
|
241
|
-
if last_update_zip and last_update_zip[0].residence_zip:
|
|
242
|
-
record.residence_zip = last_update_zip[0].residence_zip
|
|
243
|
-
|
|
244
|
-
# pylint: disable=W8110
|
|
245
|
-
@api.depends("pms_checkin_partner_ids", "pms_checkin_partner_ids.residence_city")
|
|
246
|
-
def _compute_residence_city(self):
|
|
247
|
-
if hasattr(super(), "_compute_residence_city"):
|
|
248
|
-
super()._compute_residence_city()
|
|
249
|
-
for record in self:
|
|
250
|
-
if record.pms_checkin_partner_ids:
|
|
251
|
-
last_update_city = record.pms_checkin_partner_ids.filtered(
|
|
252
|
-
lambda x, r=record: x.write_date
|
|
253
|
-
== max(r.pms_checkin_partner_ids.mapped("write_date"))
|
|
254
|
-
)
|
|
255
|
-
if last_update_city and last_update_city[0].residence_city:
|
|
256
|
-
record.residence_city = last_update_city[0].residence_city
|
|
257
|
-
|
|
258
|
-
# pylint: disable=W8110
|
|
259
|
-
@api.depends(
|
|
260
|
-
"pms_checkin_partner_ids",
|
|
261
|
-
"pms_checkin_partner_ids.residence_country_id",
|
|
262
|
-
"nationality_id",
|
|
263
|
-
)
|
|
264
|
-
def _compute_residence_country_id(self):
|
|
265
|
-
if hasattr(super(), "_compute_residence_country_id"):
|
|
266
|
-
super()._compute_residence_country_id()
|
|
267
|
-
for record in self:
|
|
268
|
-
if record.pms_checkin_partner_ids:
|
|
269
|
-
last_update_country = record.pms_checkin_partner_ids.filtered(
|
|
270
|
-
lambda x, r=record: x.write_date
|
|
271
|
-
== max(r.pms_checkin_partner_ids.mapped("write_date"))
|
|
272
|
-
)
|
|
273
|
-
if last_update_country and last_update_country[0].residence_country_id:
|
|
274
|
-
record.residence_country_id = last_update_country[
|
|
275
|
-
0
|
|
276
|
-
].residence_country_id
|
|
277
|
-
|
|
278
|
-
# pylint: disable=W8110
|
|
279
|
-
@api.depends(
|
|
280
|
-
"pms_checkin_partner_ids", "pms_checkin_partner_ids.residence_state_id"
|
|
281
|
-
)
|
|
282
|
-
def _compute_residence_state_id(self):
|
|
283
|
-
if hasattr(super(), "_compute_residence_state_id"):
|
|
284
|
-
super()._compute_residence_state_id()
|
|
285
|
-
for record in self:
|
|
286
|
-
if record.pms_checkin_partner_ids:
|
|
287
|
-
last_update_state = record.pms_checkin_partner_ids.filtered(
|
|
288
|
-
lambda x, r=record: x.write_date
|
|
289
|
-
== max(r.pms_checkin_partner_ids.mapped("write_date"))
|
|
290
|
-
)
|
|
291
|
-
if last_update_state and last_update_state[0].residence_state_id:
|
|
292
|
-
record.residence_state_id = last_update_state[0].residence_state_id
|
|
293
|
-
|
|
294
|
-
# pylint: disable=W8110
|
|
295
|
-
@api.depends("id_numbers")
|
|
296
|
-
def _compute_country_id(self):
|
|
297
|
-
if hasattr(super(), "_compute_country_id"):
|
|
298
|
-
super()._compute_country_id()
|
|
299
|
-
for record in self:
|
|
300
|
-
if (
|
|
301
|
-
not record.parent_id
|
|
302
|
-
and not record.country_id
|
|
303
|
-
and record.id_numbers
|
|
304
|
-
and record.id_numbers.country_id
|
|
305
|
-
):
|
|
306
|
-
record.country_id = record.id_numbers[0].country_id
|
|
307
|
-
|
|
308
|
-
# pylint: disable=W8110
|
|
309
|
-
@api.depends("residence_state_id")
|
|
310
|
-
def _compute_state_id(self):
|
|
311
|
-
if hasattr(super(), "_compute_state_id"):
|
|
312
|
-
super()._compute_state_id()
|
|
313
|
-
for record in self:
|
|
314
|
-
if (
|
|
315
|
-
not record.parent_id
|
|
316
|
-
and not record.state_id
|
|
317
|
-
and record.residence_state_id
|
|
318
|
-
):
|
|
319
|
-
record.state_id = record.residence_state_id
|
|
320
|
-
|
|
321
|
-
# pylint: disable=W8110
|
|
322
|
-
@api.depends("residence_city")
|
|
323
|
-
def _compute_city(self):
|
|
324
|
-
if hasattr(super(), "_compute_city"):
|
|
325
|
-
super()._compute_city()
|
|
326
|
-
for record in self:
|
|
327
|
-
if not record.parent_id and not record.city and record.residence_city:
|
|
328
|
-
record.city = record.residence_city
|
|
329
|
-
|
|
330
|
-
# pylint: disable=W8110
|
|
331
|
-
@api.depends("residence_street")
|
|
332
|
-
def _compute_street(self):
|
|
333
|
-
if hasattr(super(), "_compute_street"):
|
|
334
|
-
super()._compute_street()
|
|
335
|
-
for record in self:
|
|
336
|
-
if not record.parent_id and not record.street and record.residence_street:
|
|
337
|
-
record.street = record.residence_street
|
|
338
|
-
|
|
339
|
-
# pylint: disable=W8110
|
|
340
|
-
@api.depends("residence_street2")
|
|
341
|
-
def _compute_street2(self):
|
|
342
|
-
if hasattr(super(), "_compute_street2"):
|
|
343
|
-
super()._compute_street2()
|
|
344
|
-
for record in self:
|
|
345
|
-
if not record.parent_id and not record.street2 and record.residence_street2:
|
|
346
|
-
record.street2 = record.residence_street2
|
|
347
|
-
|
|
348
|
-
# pylint: disable=W8110
|
|
349
|
-
@api.depends("residence_zip")
|
|
350
|
-
def _compute_zip(self):
|
|
351
|
-
if hasattr(super(), "_compute_zip"):
|
|
352
|
-
super()._compute_zip()
|
|
353
|
-
for record in self:
|
|
354
|
-
if not record.parent_id and not record.zip and record.residence_zip:
|
|
355
|
-
record.zip = record.residence_zip
|
|
356
111
|
|
|
357
112
|
def _compute_reservations_count(self):
|
|
358
113
|
# Return reservation with partner included in reservation and/or checkin
|
|
@@ -494,46 +249,6 @@ class ResPartner(models.Model):
|
|
|
494
249
|
if not record.is_agency and record.sale_channel_id:
|
|
495
250
|
record.sale_channel_id = None
|
|
496
251
|
|
|
497
|
-
# REVIEW: problems with odoo demo data
|
|
498
|
-
# @api.constrains("mobile", "email")
|
|
499
|
-
# def _check_duplicated(self):
|
|
500
|
-
# for record in self:
|
|
501
|
-
# partner, field = record._search_duplicated()
|
|
502
|
-
# if partner:
|
|
503
|
-
# raise models.ValidationError(
|
|
504
|
-
# _(
|
|
505
|
-
# "Partner %s found with same %s (%s)",
|
|
506
|
-
# partner.name,
|
|
507
|
-
# partner._fields[field].string,
|
|
508
|
-
# getattr(record, field),
|
|
509
|
-
# )
|
|
510
|
-
# )
|
|
511
|
-
|
|
512
|
-
@api.constrains("residence_state_id", "residence_country_id")
|
|
513
|
-
def _check_residence_state_id_residence_country_id_consistence(self):
|
|
514
|
-
for record in self:
|
|
515
|
-
if record.residence_state_id and record.residence_country_id:
|
|
516
|
-
if (
|
|
517
|
-
record.residence_state_id.country_id
|
|
518
|
-
and record.residence_country_id
|
|
519
|
-
not in record.residence_state_id.country_id
|
|
520
|
-
):
|
|
521
|
-
raise ValidationError(
|
|
522
|
-
_("State and country of residence do not match")
|
|
523
|
-
)
|
|
524
|
-
|
|
525
|
-
def _search_duplicated(self):
|
|
526
|
-
self.ensure_one()
|
|
527
|
-
partner = False
|
|
528
|
-
for field in self._get_key_fields():
|
|
529
|
-
if getattr(self, field):
|
|
530
|
-
partner = self.search(
|
|
531
|
-
[(field, "=", getattr(self, field)), ("id", "!=", self.id)]
|
|
532
|
-
)
|
|
533
|
-
if partner:
|
|
534
|
-
field = field
|
|
535
|
-
return partner, field
|
|
536
|
-
|
|
537
252
|
@api.model
|
|
538
253
|
def _get_key_fields(self):
|
|
539
254
|
key_fields = super()._get_key_fields()
|
|
@@ -545,87 +260,6 @@ class ResPartner(models.Model):
|
|
|
545
260
|
# Template to be inherited by localization modules
|
|
546
261
|
return True
|
|
547
262
|
|
|
548
|
-
def unlink(self):
|
|
549
|
-
various_partner_id = self.env.ref("pms.various_pms_partner").id
|
|
550
|
-
if various_partner_id in self.ids:
|
|
551
|
-
various_partner = self.browse(various_partner_id)
|
|
552
|
-
raise ValidationError(
|
|
553
|
-
_("The partner %s cannot be deleted"), various_partner.name
|
|
554
|
-
)
|
|
555
|
-
return super().unlink()
|
|
556
|
-
|
|
557
|
-
@api.model_create_multi
|
|
558
|
-
def create(self, vals_list):
|
|
559
|
-
for vals in vals_list:
|
|
560
|
-
check_missing_document = self._check_document_partner_required(vals)
|
|
561
|
-
if check_missing_document:
|
|
562
|
-
raise ValidationError(_("A document identification is required"))
|
|
563
|
-
return super().create(vals_list)
|
|
564
|
-
|
|
565
|
-
def write(self, vals):
|
|
566
|
-
check_missing_document = self._check_document_partner_required(
|
|
567
|
-
vals, partners=self
|
|
568
|
-
)
|
|
569
|
-
if check_missing_document:
|
|
570
|
-
# REVIEW: Deactivate this check for now, because it can generate problems
|
|
571
|
-
# with other modules that update technical partner fields
|
|
572
|
-
_logger.warning(
|
|
573
|
-
_("Partner without document identification, update vals %s"), vals
|
|
574
|
-
)
|
|
575
|
-
# We only check if the vat or document_number is updated
|
|
576
|
-
if "vat" in vals or "document_number" in vals:
|
|
577
|
-
raise ValidationError(_("A document identification is required"))
|
|
578
|
-
return super().write(vals)
|
|
579
|
-
|
|
580
|
-
@api.model
|
|
581
|
-
def _check_document_partner_required(self, vals, partners=False):
|
|
582
|
-
company_ids = (
|
|
583
|
-
self.env["res.company"].sudo().search([]).ids
|
|
584
|
-
if (not partners or any([not partner.company_id for partner in partners]))
|
|
585
|
-
else partners.mapped("company_id.id")
|
|
586
|
-
)
|
|
587
|
-
if not self.env.context.get("avoid_document_restriction") and any(
|
|
588
|
-
[
|
|
589
|
-
self.env["res.company"]
|
|
590
|
-
.sudo()
|
|
591
|
-
.browse(company_id)
|
|
592
|
-
.document_partner_required
|
|
593
|
-
for company_id in company_ids
|
|
594
|
-
]
|
|
595
|
-
):
|
|
596
|
-
return self._missing_document(vals, partners)
|
|
597
|
-
return False
|
|
598
|
-
|
|
599
|
-
@api.model
|
|
600
|
-
def _missing_document(self, vals, partners=False):
|
|
601
|
-
# If not is a partner contact and not have vat,
|
|
602
|
-
# then return missing document True
|
|
603
|
-
if (
|
|
604
|
-
not vals.get("parent_id")
|
|
605
|
-
or (partners and any([not partner.parent_id for partner in partners]))
|
|
606
|
-
) and (
|
|
607
|
-
vals.get("vat") is False
|
|
608
|
-
or vals.get("vat") == ""
|
|
609
|
-
or (
|
|
610
|
-
"vat" not in vals
|
|
611
|
-
and (
|
|
612
|
-
any([not partner.vat for partner in partners]) if partners else True
|
|
613
|
-
)
|
|
614
|
-
)
|
|
615
|
-
or vals.get("country_id") is False
|
|
616
|
-
or vals.get("country_id") == ""
|
|
617
|
-
or (
|
|
618
|
-
"country_id" not in vals
|
|
619
|
-
and (
|
|
620
|
-
any([not partner.country_id for partner in partners])
|
|
621
|
-
if partners
|
|
622
|
-
else True
|
|
623
|
-
)
|
|
624
|
-
)
|
|
625
|
-
):
|
|
626
|
-
return True
|
|
627
|
-
return False
|
|
628
|
-
|
|
629
263
|
@api.constrains("is_agency", "property_product_pricelist")
|
|
630
264
|
def _check_agency_pricelist(self):
|
|
631
265
|
if any(
|
|
@@ -372,7 +372,7 @@ ul.auto-toc {
|
|
|
372
372
|
!! This file is generated by oca-gen-addon-readme !!
|
|
373
373
|
!! changes will be overwritten. !!
|
|
374
374
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
375
|
-
!! source digest: sha256:
|
|
375
|
+
!! source digest: sha256:fdad945e7d2a775c8552d512894f9bc9825d1e46f727cc056aa22913f7c91600
|
|
376
376
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
|
|
377
377
|
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/license-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/pms/tree/16.0/pms"><img alt="OCA/pms" src="https://img.shields.io/badge/github-OCA%2Fpms-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/pms-16-0/pms-16-0-pms"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/pms&target_branch=16.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
|
|
378
378
|
<p>This module is an all-in-one property management system (PMS) focused on medium-sized properties
|